import { useState } from 'react';
import {
Plus,
Trash2,
MapPin,
ChevronRight,
Copy,
Crown,
} from 'lucide-react';
import { usePostTypes } from '../../hooks/usePostTypes';
import { useTaxonomies } from '../../hooks/useTaxonomies';
import { useProStatus } from '../../hooks/use-pro-status';
// Location rule types
interface LocationRule {
param: string;
operator: string;
value: string;
}
// Location rule group (AND logic within group)
type LocationRuleGroup = LocationRule[];
// Location param option type
interface LocationParamOption {
value: string;
label: string;
group: string;
}
// Location param options for Free version
const FREE_LOCATION_PARAMS: LocationParamOption[] = [
{ value: 'post_type', label: 'Post Type', group: 'Post' },
{ value: 'post', label: 'Post', group: 'Post' },
{ value: 'post_status', label: 'Post Status', group: 'Post' },
{ value: 'taxonomy', label: 'Taxonomy', group: 'Term' },
{ value: 'term', label: 'Term', group: 'Term' },
{ value: 'user_role', label: 'User Role', group: 'User' },
{ value: 'options_page', label: 'Options Page', group: 'Options' },
];
// Pro location params (shown with PRO badge)
const PRO_LOCATION_PARAMS: LocationParamOption[] = [
{ value: 'page_template', label: 'Page Template', group: 'Post' },
{ value: 'post_parent', label: 'Post Parent', group: 'Post' },
{ value: 'post_author', label: 'Post Author', group: 'Post' },
{ value: 'post_format', label: 'Post Format', group: 'Post' },
{ value: 'post_taxonomy', label: 'Post Taxonomy Term', group: 'Post' },
{ value: 'current_user_role', label: 'Current User Role', group: 'User' },
{ value: 'current_user_capability', label: 'Current User Capability', group: 'User' },
];
// Location operators
const LOCATION_OPERATORS = [
{ value: '==', label: 'is equal to' },
{ value: '!=', label: 'is not equal to' },
];
// Collapsible Section Component
function CollapsibleSection({
title,
icon,
children,
defaultOpen = true,
badge
}: {
title: string;
icon: React.ReactNode;
children: React.ReactNode;
defaultOpen?: boolean;
badge?: string;
}) {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
{isOpen && (
{children}
)}
);
}
// Props interface
interface LocationRulesBuilderProps {
locations: LocationRuleGroup[];
setLocations: React.Dispatch>;
}
export function LocationRulesBuilder({
locations,
setLocations
}: LocationRulesBuilderProps) {
const { isPro } = useProStatus();
const { data: postTypes } = usePostTypes();
const { data: taxonomies } = useTaxonomies();
// Get all available post types (built-in + custom)
const availablePostTypes = [
{ slug: 'post', label: 'Post' },
{ slug: 'page', label: 'Page' },
...(postTypes?.map(pt => ({ slug: pt.slug, label: pt.title })) || []),
];
// Get all available taxonomies (built-in + custom)
const availableTaxonomies = [
{ slug: 'category', label: 'Category' },
{ slug: 'post_tag', label: 'Tag' },
...(taxonomies?.map(tax => ({ slug: tax.slug, label: tax.title })) || []),
];
// Post status options
const postStatuses = [
{ value: 'publish', label: 'Published' },
{ value: 'pending', label: 'Pending Review' },
{ value: 'draft', label: 'Draft' },
{ value: 'future', label: 'Scheduled' },
{ value: 'private', label: 'Private' },
];
// User role options
const userRoles = [
{ value: 'administrator', label: 'Administrator' },
{ value: 'editor', label: 'Editor' },
{ value: 'author', label: 'Author' },
{ value: 'contributor', label: 'Contributor' },
{ value: 'subscriber', label: 'Subscriber' },
];
// Get value options based on param type
const getValueOptions = (param: string) => {
switch (param) {
case 'post_type':
return availablePostTypes.map(pt => ({ value: pt.slug, label: pt.label }));
case 'taxonomy':
return availableTaxonomies.map(tax => ({ value: tax.slug, label: tax.label }));
case 'post_status':
return postStatuses;
case 'user_role':
case 'current_user_role':
return userRoles;
default:
return [];
}
};
// Check if param should have a text input instead of select
const isTextInput = (param: string) => {
return ['post', 'term', 'post_parent', 'post_author', 'page_template', 'options_page', 'current_user_capability'].includes(param);
};
// Check if param is Pro only
const isProParam = (param: string) => {
return PRO_LOCATION_PARAMS.some((p: LocationParamOption) => p.value === param);
};
// Add a new rule to a group
const addRule = (groupIndex: number) => {
setLocations(prev => prev.map((group: LocationRuleGroup, idx: number) => {
if (idx === groupIndex) {
return [...group, { param: 'post_type', operator: '==', value: 'post' }];
}
return group;
}));
};
// Update a rule in a group
const updateRule = (groupIndex: number, ruleIndex: number, updates: Partial) => {
setLocations(prev => prev.map((group: LocationRuleGroup, gIdx: number) => {
if (gIdx === groupIndex) {
return group.map((rule: LocationRule, rIdx: number) => {
if (rIdx === ruleIndex) {
// If param changes, reset the value
if (updates.param && updates.param !== rule.param) {
const defaultOptions = getValueOptions(updates.param);
return {
...rule,
...updates,
value: defaultOptions.length > 0 ? defaultOptions[0].value : ''
};
}
return { ...rule, ...updates };
}
return rule;
});
}
return group;
}));
};
// Remove a rule from a group
const removeRule = (groupIndex: number, ruleIndex: number) => {
setLocations(prev => prev.map((group: LocationRuleGroup, gIdx: number) => {
if (gIdx === groupIndex) {
// Don't remove if it's the last rule
if (group.length === 1) return group;
return group.filter((_: LocationRule, rIdx: number) => rIdx !== ruleIndex);
}
return group;
}));
};
// Add a new OR group (Pro feature)
const addGroup = () => {
if (!isPro) return;
setLocations(prev => [...prev, [{ param: 'post_type', operator: '==', value: 'post' }]]);
};
// Duplicate a group
const duplicateGroup = (groupIndex: number) => {
if (!isPro) return;
setLocations(prev => {
const newGroups = [...prev];
const groupToCopy = prev[groupIndex].map((rule: LocationRule) => ({ ...rule }));
newGroups.splice(groupIndex + 1, 0, groupToCopy);
return newGroups;
});
};
// Remove a group
const removeGroup = (groupIndex: number) => {
// Don't remove if it's the last group
if (locations.length === 1) return;
setLocations(prev => prev.filter((_: LocationRuleGroup, idx: number) => idx !== groupIndex));
};
// Generate human-readable summary
const getLocationSummary = () => {
const allParams = [...FREE_LOCATION_PARAMS, ...PRO_LOCATION_PARAMS];
const groupSummaries = locations.map((group: LocationRuleGroup) => {
const ruleSummaries = group.map((rule: LocationRule) => {
const paramLabel = allParams.find((p: LocationParamOption) => p.value === rule.param)?.label || rule.param;
const operatorLabel = rule.operator === '==' ? 'is' : 'is not';
const valueOptions = getValueOptions(rule.param);
const valueLabel = valueOptions.find((v: { value: string; label: string }) => v.value === rule.value)?.label || rule.value;
return `${paramLabel} ${operatorLabel} "${valueLabel}"`;
});
return ruleSummaries.join(' AND ');
});
if (groupSummaries.length === 1) {
return groupSummaries[0];
}
return groupSummaries.map((s: string, i: number) => `Group ${i + 1}: ${s}`).join(' OR ');
};
// Group params by category for the dropdown
const groupedParams = () => {
const groups: { [key: string]: { value: string; label: string; isPro?: boolean }[] } = {
'Post': [],
'Term': [],
'User': [],
'Options': [],
};
FREE_LOCATION_PARAMS.forEach((p: LocationParamOption) => {
if (groups[p.group]) {
groups[p.group].push({ value: p.value, label: p.label, isPro: false });
}
});
PRO_LOCATION_PARAMS.forEach((p: LocationParamOption) => {
if (groups[p.group]) {
groups[p.group].push({ value: p.value, label: p.label, isPro: true });
}
});
return groups;
};
const paramGroups = groupedParams();
return (
}
badge={locations.length > 1 ? `${locations.length} groups` : `${locations[0]?.length || 0} rules`}
>
{/* Location Summary */}
Show this field group if
{getLocationSummary() || 'No rules configured'}
{/* Rule Groups */}
{locations.map((group: LocationRuleGroup, groupIndex: number) => (
{/* OR separator between groups */}
{groupIndex > 0 && (
)}
{/* Group Card */}
{/* Group Header */}
{locations.length > 1 && (
Rule Group {groupIndex + 1}
)}
{/* Rules */}
{group.map((rule: LocationRule, ruleIndex: number) => {
const valueOptions = getValueOptions(rule.param);
const isText = isTextInput(rule.param);
const isParamPro = isProParam(rule.param);
return (
{/* AND separator between rules */}
{ruleIndex > 0 && (
and
)}
{/* Rule Row */}
{/* Param Select */}
{/* Operator Select */}
{/* Value Input/Select */}
{isText ? (
updateRule(groupIndex, ruleIndex, { value: e.target.value })}
placeholder={rule.param === 'post' ? 'Enter post ID or slug' : 'Enter value...'}
className="rdcfe-h-10 rdcfe-w-full rdcfe-rounded-lg rdcfe-border rdcfe-border-[hsl(var(--rdcfe-border))] rdcfe-bg-white rdcfe-px-3 rdcfe-text-[13px] rdcfe-text-[hsl(var(--rdcfe-foreground))] rdcfe-placeholder-[hsl(var(--rdcfe-muted-foreground))] rdcfe-transition-all focus:rdcfe-border-[hsl(var(--rdcfe-primary))] focus:rdcfe-outline-none focus:rdcfe-ring-2 focus:rdcfe-ring-[hsl(var(--rdcfe-primary)/0.2)]"
/>
) : (
)}
{/* Pro Badge for Pro params */}
{isParamPro && (
Pro
)}
{/* Remove Rule Button */}
{group.length > 1 && (
)}
);
})}
{/* Add Rule Button */}
))}
{/* Add OR Group Button (Pro) */}
{!isPro && (
OR groups allow you to show field groups when any group of rules matches
)}
);
}