fix: WP-Admin CSS conflicts and add-to-cart redirect

- Fix CSS conflicts between WP-Admin and SPA (radio buttons, chart text)
- Add Tailwind important selector scoped to #woonoow-admin-app
- Remove overly aggressive inline SVG styles from Assets.php
- Add targeted WordPress admin CSS overrides in index.css
- Fix add-to-cart redirect to use woocommerce_add_to_cart_redirect filter
- Let WooCommerce handle cart operations natively for proper session management
- Remove duplicate tailwind.config.cjs
This commit is contained in:
Dwindi Ramadhana
2025-12-31 14:06:04 +07:00
parent 93523a74ac
commit 82399d4ddf
20 changed files with 1272 additions and 571 deletions

View File

@@ -76,6 +76,43 @@
}
}
/* ============================================
WordPress Admin Override Fixes
These rules use high specificity + !important
to override WordPress admin CSS conflicts
============================================ */
/* Fix SVG icon styling - WordPress sets fill:currentColor on all SVGs */
#woonoow-admin-app svg {
fill: none !important;
}
/* But allow explicit fill-current class to work for filled icons */
#woonoow-admin-app svg.fill-current,
#woonoow-admin-app .fill-current svg,
#woonoow-admin-app [class*="fill-"] svg {
fill: currentColor !important;
}
/* Fix radio button indicator - WordPress overrides circle fill */
#woonoow-admin-app [data-radix-radio-group-item] svg,
#woonoow-admin-app [role="radio"] svg {
fill: currentColor !important;
}
/* Fix font-weight inheritance - prevent WordPress bold overrides */
#woonoow-admin-app text,
#woonoow-admin-app tspan {
font-weight: inherit !important;
}
/* Reset form element styling that WordPress overrides */
#woonoow-admin-app input[type="radio"],
#woonoow-admin-app input[type="checkbox"] {
appearance: none !important;
-webkit-appearance: none !important;
}
/* Command palette input: remove native borders/shadows to match shadcn */
.command-palette-search {
border: none !important;

View File

@@ -127,6 +127,7 @@ export default function ProductEdit() {
onSubmit={handleSubmit}
formRef={formRef}
hideSubmitButton={true}
productId={product.id}
/>
{/* Level 1 compatibility: Custom meta fields from plugins */}

View File

@@ -0,0 +1,215 @@
import React, { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Copy, Check, ExternalLink } from 'lucide-react';
import { toast } from 'sonner';
interface DirectCartLinksProps {
productId: number;
productType: 'simple' | 'variable';
variations?: Array<{
id: number;
name: string;
attributes: Record<string, string>;
}>;
}
export function DirectCartLinks({ productId, productType, variations = [] }: DirectCartLinksProps) {
const [quantity, setQuantity] = useState(1);
const [copiedLink, setCopiedLink] = useState<string | null>(null);
const siteUrl = window.location.origin;
const spaPagePath = '/store'; // This should ideally come from settings
const generateLink = (variationId?: number, redirect: 'cart' | 'checkout' = 'cart') => {
const params = new URLSearchParams();
params.set('add-to-cart', productId.toString());
if (variationId) {
params.set('variation_id', variationId.toString());
}
if (quantity > 1) {
params.set('quantity', quantity.toString());
}
params.set('redirect', redirect);
return `${siteUrl}${spaPagePath}?${params.toString()}`;
};
const copyToClipboard = async (link: string, label: string) => {
try {
await navigator.clipboard.writeText(link);
setCopiedLink(link);
toast.success(`${label} link copied!`);
setTimeout(() => setCopiedLink(null), 2000);
} catch (err) {
toast.error('Failed to copy link');
}
};
const LinkRow = ({
label,
link,
description
}: {
label: string;
link: string;
description?: string;
}) => {
const isCopied = copiedLink === link;
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium">{label}</Label>
<div className="flex gap-2">
<Button
type="button"
size="sm"
variant="outline"
onClick={() => copyToClipboard(link, label)}
>
{isCopied ? (
<>
<Check className="h-4 w-4 mr-1" />
Copied
</>
) : (
<>
<Copy className="h-4 w-4 mr-1" />
Copy
</>
)}
</Button>
<Button
type="button"
size="sm"
variant="ghost"
onClick={() => window.open(link, '_blank')}
>
<ExternalLink className="h-4 w-4" />
</Button>
</div>
</div>
<Input
value={link}
readOnly
className="font-mono text-xs"
onClick={(e) => e.currentTarget.select()}
/>
{description && (
<p className="text-xs text-muted-foreground">{description}</p>
)}
</div>
);
};
return (
<Card>
<CardHeader>
<CardTitle>Direct-to-Cart Links</CardTitle>
<CardDescription>
Generate copyable links that add this product to cart and redirect to cart or checkout page.
Perfect for landing pages, email campaigns, and social media.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Quantity Selector */}
<div className="space-y-2">
<Label htmlFor="link-quantity">Default Quantity</Label>
<Input
id="link-quantity"
type="number"
min="1"
value={quantity}
onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}
className="w-32"
/>
<p className="text-xs text-muted-foreground">
Set quantity to 1 to exclude from URL (cleaner links)
</p>
</div>
{/* Simple Product Links */}
{productType === 'simple' && (
<div className="space-y-4">
<div className="border-b pb-2">
<h4 className="font-medium">Simple Product Links</h4>
</div>
<LinkRow
label="Add to Cart"
link={generateLink(undefined, 'cart')}
description="Adds product to cart and shows cart page"
/>
<LinkRow
label="Direct to Checkout"
link={generateLink(undefined, 'checkout')}
description="Adds product to cart and goes directly to checkout"
/>
</div>
)}
{/* Variable Product Links */}
{productType === 'variable' && variations.length > 0 && (
<div className="space-y-4">
<div className="border-b pb-2">
<h4 className="font-medium">Variable Product Links</h4>
<p className="text-xs text-muted-foreground mt-1">
{variations.length} variation(s) - Select a variation to generate links
</p>
</div>
<div className="space-y-2">
{variations.map((variation, index) => (
<details key={variation.id} className="group border rounded-lg">
<summary className="cursor-pointer p-3 hover:bg-muted/50 flex items-center justify-between">
<div className="flex-1">
<span className="font-medium text-sm">{variation.name}</span>
<span className="text-xs text-muted-foreground ml-2">
(ID: {variation.id})
</span>
</div>
<svg
className="w-4 h-4 transition-transform group-open:rotate-180"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div className="p-4 pt-0 space-y-3 border-t">
<LinkRow
label="Add to Cart"
link={generateLink(variation.id, 'cart')}
/>
<LinkRow
label="Direct to Checkout"
link={generateLink(variation.id, 'checkout')}
/>
</div>
</details>
))}
</div>
</div>
)}
{/* URL Parameters Reference */}
<div className="mt-6 p-4 bg-muted rounded-lg">
<h4 className="font-medium text-sm mb-2">URL Parameters Reference</h4>
<div className="space-y-1 text-xs text-muted-foreground">
<div><code className="bg-background px-1 py-0.5 rounded">add-to-cart</code> - Product ID (required)</div>
<div><code className="bg-background px-1 py-0.5 rounded">variation_id</code> - Variation ID (for variable products)</div>
<div><code className="bg-background px-1 py-0.5 rounded">quantity</code> - Quantity (default: 1)</div>
<div><code className="bg-background px-1 py-0.5 rounded">redirect</code> - Destination: <code>cart</code> or <code>checkout</code></div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -41,6 +41,7 @@ type Props = {
className?: string;
formRef?: React.RefObject<HTMLFormElement>;
hideSubmitButton?: boolean;
productId?: number;
};
export function ProductFormTabbed({
@@ -50,6 +51,7 @@ export function ProductFormTabbed({
className,
formRef,
hideSubmitButton = false,
productId,
}: Props) {
// Form state
const [name, setName] = useState(initial?.name || '');
@@ -225,6 +227,7 @@ export function ProductFormTabbed({
variations={variations}
setVariations={setVariations}
regularPrice={regularPrice}
productId={productId}
/>
</FormSection>
)}

View File

@@ -7,7 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Plus, X, Layers, Image as ImageIcon } from 'lucide-react';
import { Plus, X, Layers, Image as ImageIcon, Copy, Check, ExternalLink } from 'lucide-react';
import { toast } from 'sonner';
import { getStoreCurrency } from '@/lib/currency';
import { openWPMediaImage } from '@/lib/wp-media';
@@ -30,6 +30,7 @@ type VariationsTabProps = {
variations: ProductVariant[];
setVariations: (value: ProductVariant[]) => void;
regularPrice: string;
productId?: number;
};
export function VariationsTab({
@@ -38,8 +39,33 @@ export function VariationsTab({
variations,
setVariations,
regularPrice,
productId,
}: VariationsTabProps) {
const store = getStoreCurrency();
const [copiedLink, setCopiedLink] = useState<string | null>(null);
const siteUrl = window.location.origin;
const spaPagePath = '/store';
const generateLink = (variationId: number, redirect: 'cart' | 'checkout' = 'cart') => {
if (!productId) return '';
const params = new URLSearchParams();
params.set('add-to-cart', productId.toString());
params.set('variation_id', variationId.toString());
params.set('redirect', redirect);
return `${siteUrl}${spaPagePath}?${params.toString()}`;
};
const copyToClipboard = async (link: string, label: string) => {
try {
await navigator.clipboard.writeText(link);
setCopiedLink(link);
toast.success(`${label} link copied!`);
setTimeout(() => setCopiedLink(null), 2000);
} catch (err) {
toast.error('Failed to copy link');
}
};
const addAttribute = () => {
setAttributes([...attributes, { name: '', options: [], variation: false }]);
@@ -305,6 +331,45 @@ export function VariationsTab({
}}
/>
</div>
{/* Direct Cart Links */}
{productId && variation.id && (
<div className="mt-4 pt-4 border-t space-y-2">
<Label className="text-xs font-medium text-muted-foreground">
{__('Direct-to-Cart Links')}
</Label>
<div className="flex gap-2">
<Button
type="button"
size="sm"
variant="outline"
onClick={() => copyToClipboard(generateLink(variation.id!, 'cart'), 'Cart')}
className="flex-1"
>
{copiedLink === generateLink(variation.id!, 'cart') ? (
<Check className="h-3 w-3 mr-1" />
) : (
<Copy className="h-3 w-3 mr-1" />
)}
{__('Copy Cart Link')}
</Button>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => copyToClipboard(generateLink(variation.id!, 'checkout'), 'Checkout')}
className="flex-1"
>
{copiedLink === generateLink(variation.id!, 'checkout') ? (
<Check className="h-3 w-3 mr-1" />
) : (
<Copy className="h-3 w-3 mr-1" />
)}
{__('Copy Checkout Link')}
</Button>
</div>
</div>
)}
</CardContent>
</Card>
))}

View File

@@ -1,61 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./src/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
theme: {
container: {
center: true,
padding: '1rem'
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
}
}
},
plugins: [require("tailwindcss-animate")]
};

View File

@@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
important: '#woonoow-admin-app',
content: ["./src/**/*.{ts,tsx,css}", "./components/**/*.{ts,tsx,css}"],
theme: {
container: { center: true, padding: "1rem" },