feat: Tax settings + unified addon guide + Biteship spec
## 1. Created BITESHIP_ADDON_SPEC.md ✅ - Complete plugin specification - Database schema, API endpoints - WooCommerce integration - React components - Implementation timeline ## 2. Merged Addon Documentation ✅ Created ADDON_DEVELOPMENT_GUIDE.md (single source of truth): - Merged ADDON_INJECTION_GUIDE.md + ADDON_HOOK_SYSTEM.md - Two addon types: Route Injection + Hook System - Clear examples for each type - Best practices and troubleshooting - Deleted old documents ## 3. Tax Settings ✅ Frontend (admin-spa/src/routes/Settings/Tax.tsx): - Enable/disable tax calculation toggle - Display standard/reduced/zero tax rates - Show tax options (prices include tax, based on, display) - Link to WooCommerce for advanced config - Clean, simple UI Backend (includes/Api/TaxController.php): - GET /settings/tax - Fetch tax settings - POST /settings/tax/toggle - Enable/disable taxes - Fetches rates from woocommerce_tax_rates table - Clears WooCommerce cache on update ## 4. Advanced Local Pickup - TODO Will be simple: Admin adds multiple pickup locations ## Key Decisions: ✅ Hook system = No hardcoding, zero coupling ✅ Tax settings = Simple toggle + view, advanced in WC ✅ Single addon guide = One source of truth Next: Advanced Local Pickup locations
This commit is contained in:
@@ -196,6 +196,7 @@ import SettingsIndex from '@/routes/Settings';
|
||||
import SettingsStore from '@/routes/Settings/Store';
|
||||
import SettingsPayments from '@/routes/Settings/Payments';
|
||||
import SettingsShipping from '@/routes/Settings/Shipping';
|
||||
import SettingsTax from '@/routes/Settings/Tax';
|
||||
import MorePage from '@/routes/More';
|
||||
|
||||
// Addon Route Component - Dynamically loads addon components
|
||||
@@ -426,7 +427,7 @@ function AppRoutes() {
|
||||
<Route path="/settings/store" element={<SettingsStore />} />
|
||||
<Route path="/settings/payments" element={<SettingsPayments />} />
|
||||
<Route path="/settings/shipping" element={<SettingsShipping />} />
|
||||
<Route path="/settings/taxes" element={<SettingsIndex />} />
|
||||
<Route path="/settings/tax" element={<SettingsTax />} />
|
||||
<Route path="/settings/checkout" element={<SettingsIndex />} />
|
||||
<Route path="/settings/customers" element={<SettingsIndex />} />
|
||||
<Route path="/settings/notifications" element={<SettingsIndex />} />
|
||||
|
||||
309
admin-spa/src/routes/Settings/Tax.tsx
Normal file
309
admin-spa/src/routes/Settings/Tax.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
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 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { __ } from '@/lib/i18n';
|
||||
|
||||
export default function TaxSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
||||
|
||||
// Fetch tax settings
|
||||
const { data: settings, isLoading, refetch } = useQuery({
|
||||
queryKey: ['tax-settings'],
|
||||
queryFn: () => api.get('/settings/tax'),
|
||||
});
|
||||
|
||||
// Toggle tax calculation
|
||||
const toggleMutation = useMutation({
|
||||
mutationFn: async (enabled: boolean) => {
|
||||
return api.post('/settings/tax/toggle', { enabled });
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tax-settings'] });
|
||||
toast.success(__('Tax settings updated'));
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error?.message || __('Failed to update tax settings'));
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SettingsLayout
|
||||
title={__('Tax')}
|
||||
description={__('Configure tax calculation and rates')}
|
||||
>
|
||||
<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={__('Tax')}
|
||||
description={__('Configure tax calculation and rates')}
|
||||
action={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
{__('Refresh')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Enable Tax Calculation */}
|
||||
<SettingsCard
|
||||
title={__('Tax Calculation')}
|
||||
description={__('Enable or disable tax calculation for your store')}
|
||||
>
|
||||
<ToggleField
|
||||
label={__('Enable tax rates and calculations')}
|
||||
description={__('When enabled, taxes will be calculated based on customer location and product tax class')}
|
||||
checked={settings?.calc_taxes === 'yes'}
|
||||
onChange={(checked) => toggleMutation.mutate(checked)}
|
||||
disabled={toggleMutation.isPending}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
{/* Tax Rates */}
|
||||
{settings?.calc_taxes === 'yes' && (
|
||||
<SettingsCard
|
||||
title={__('Tax Rates')}
|
||||
description={__('Configure tax rates for different locations and tax classes')}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4 bg-muted/50">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium mb-1">{__('Standard Rates')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{__('Tax rates applied to standard products')}
|
||||
</p>
|
||||
{settings?.standard_rates && settings.standard_rates.length > 0 ? (
|
||||
<div className="mt-3 space-y-2">
|
||||
{settings.standard_rates.map((rate: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<span>{rate.country} {rate.state && `- ${rate.state}`}</span>
|
||||
<span className="font-medium">{rate.rate}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{__('No standard rates configured')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax§ion=standard`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
{__('Manage')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4 bg-muted/50">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium mb-1">{__('Reduced Rates')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{__('Lower tax rates for specific products')}
|
||||
</p>
|
||||
{settings?.reduced_rates && settings.reduced_rates.length > 0 ? (
|
||||
<div className="mt-3 space-y-2">
|
||||
{settings.reduced_rates.map((rate: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<span>{rate.country} {rate.state && `- ${rate.state}`}</span>
|
||||
<span className="font-medium">{rate.rate}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{__('No reduced rates configured')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax§ion=reduced-rate`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
{__('Manage')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4 bg-muted/50">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium mb-1">{__('Zero Rates')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{__('No tax for specific products or locations')}
|
||||
</p>
|
||||
{settings?.zero_rates && settings.zero_rates.length > 0 ? (
|
||||
<div className="mt-3 space-y-2">
|
||||
{settings.zero_rates.map((rate: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between text-sm">
|
||||
<span>{rate.country} {rate.state && `- ${rate.state}`}</span>
|
||||
<span className="font-medium">0%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{__('No zero rates configured')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax§ion=zero-rate`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
{__('Manage')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{/* Tax Options */}
|
||||
{settings?.calc_taxes === 'yes' && (
|
||||
<SettingsCard
|
||||
title={__('Tax Options')}
|
||||
description={__('Additional tax calculation settings')}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<p className="font-medium text-sm">{__('Prices entered with tax')}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{settings?.prices_include_tax === 'yes'
|
||||
? __('Product prices include tax')
|
||||
: __('Product prices exclude tax')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax`}
|
||||
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">{__('Calculate tax based on')}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{settings?.tax_based_on === 'shipping' && __('Customer shipping address')}
|
||||
{settings?.tax_based_on === 'billing' && __('Customer billing address')}
|
||||
{settings?.tax_based_on === 'base' && __('Shop base address')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax`}
|
||||
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">{__('Display prices in shop')}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{settings?.tax_display_shop === 'incl' && __('Including tax')}
|
||||
{settings?.tax_display_shop === 'excl' && __('Excluding tax')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax`}
|
||||
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 advanced tax configuration, use the WooCommerce settings page')}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=tax`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
{__('Open Tax Settings in WooCommerce')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user