This commit is contained in:
Jeremy Rangel
2025-02-06 09:05:18 -08:00
commit 60006e6188
11 changed files with 20590 additions and 0 deletions

View File

@ -0,0 +1,237 @@
import { useDrag, useDrop } from 'react-dnd';
import { DateTimePicker,SelectControl, TextControl, __experimentalNumberControl as NumberControl } from '@wordpress/components';
import { useState, useEffect, useCallback } from '@wordpress/element';
const DraggableItem = ({ id, index, moveItem, postType, children }) => {
// Drag-and-drop hooks with memoization
const [{ isDragging }, drag] = useDrag(
useCallback(() => ({
type: 'rule',
item: { id, index },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}), [id, index])
);
const [, drop] = useDrop(
useCallback(() => ({
accept: 'rule',
hover: (item) => {
if (item.index !== index) {
moveItem(item.index, index);
item.index = index; // Keep track of the moved index
}
},
}), [index, moveItem])
);
// State management
const [ruleType, setRuleType] = useState('post');
const [lockType, setLockType] = useState('has_subscription');
const [operator, setOperator] = useState('=');
const [metaKey, setMetaKey] = useState('');
const [roles, setRoles] = useState([]); // State to store user roles
const [taxonomies, setTaxonomies] = useState([]); // State to store taxonomies
const [terms, setTerms] = useState([]); // State to store terms of a selected taxonomy
const [postContentLengthAmount, setPostContentLengthAmount] = useState(0);
const [postContentLengthUnit, setPostContentLengthUnit] = useState('characters');
const [postContentLengthOperator, setPostContentLengthOperator] = useState('=');
const [postDate,setPostDate] = useState('')
const [postDateOperator, setPostDateOperator] = useState('=');
const [metaKeyOperator, setMetaKeyOperator] = useState('=');
const [metaValue, setMetaValue] = useState('');
// Fetch user roles from the WordPress REST API
useEffect(() => {
if (lockType === 'has_role') {
fetch('/localcontentpro/wp-json/lcp-paywall/v1/user-roles') // Your custom REST API endpoint
.then((response) => response.json())
.then((data) => {
setRoles(data.map(role => ({ label: role.name, value: role.key })));
})
.catch((error) => console.error('Error fetching roles:', error));
}
}, [lockType]); // Fetch only when lockType is 'has_role'
// Fetch taxonomies for the current post type
useEffect(() => {
if ((lockType === 'in_term' || lockType === 'not_in_term') && postType) {
fetch(`/localcontentpro/wp-json/lcp-paywall/v1/taxonomies?post_type=${postType}`)
.then(response => response.json())
.then(data => {
const taxonomyOptions = Object.keys(data).map(taxonomy => ({
label: data[taxonomy].name,
value: taxonomy,
}));
setTaxonomies(taxonomyOptions);
})
.catch(error => console.error('Error fetching taxonomies:', error));
}
}, [lockType, postType]); // Fetch taxonomies when lockType is 'in_term' and postType changes
// Fetch terms when lockType is in_term or not_in_term and a taxonomy is selected
useEffect(() => {
if ((lockType === 'in_term' || lockType === 'not_in_term') && metaKey) {
fetch(`/localcontentpro/wp-json/lcp-paywall/v1/terms?taxonomy=${metaKey}`)
.then(response => response.json())
.then(data => {
const termOptions = data.map(term => ({
label: term.name,
value: term.id,
}));
setTerms(termOptions);
})
.catch(error => console.error('Error fetching terms:', error));
}
}, [lockType, metaKey]); // Fetch terms when lockType is 'in_term' or 'not_in_term' and metaKey (taxonomy) is selected
const postLockTypeOptions = [
{ value: 'post_content_length', label: 'Post Content Length' },
{ value: 'post_in', label: 'Post In' },
{value: 'post_not_in', label: 'Post Not In' },
{ value: 'meta_value', label: 'Meta Value' },
{ value: 'in_term', label: 'In Term' },
{ value: 'not_in_term', label: 'Not In Term' },
{ value: 'older_than', label: 'Older Than' },
{ value: 'newer_than', label: 'Newer Than' },
{ value: 'author_in', label: 'Author In' },
{ value: 'author_not_in', label: 'Author Not In' },
{ value: 'post_date', label: 'Post Date' }
];
const userLockTypeOptions = [
{ label: 'Has Subscription', value: 'has_subscription' },
{ label: 'Is Logged In', value: 'is_logged_in' },
{ label: 'Role In', value: 'role_in' },
{ label: 'Role Not In', value: 'role_not_in' },
{ label: 'Meta Value', value: 'meta_value' }
];
const userDeviceLockTypeOptions = [
{ label: 'Browser In', value: 'browser_in' },
{ label: 'Browser Not In', value: 'browser_not_in' },
{ label: 'Operating System In', value: 'os_in' },
{ label: 'Operating System Not In', value: 'os_not_in' }
];
const comparisonOperators = [
{ label: '=', value: '=' },
{ label: '!=', value: '!=' },
{ label: '>', value: '>' },
{ label: '<', value: '<' },
{ label: '>=', value: '>=' },
{ label: '<=', value: '<=' },
];
return (
<div
ref={(node) => drag(drop(node))}
className={`lcp-paywall-draggable-item ${isDragging ? 'is-dragging' : ''}`}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
<div className="lcp-paywall-rule-controls">
<SelectControl
value={ruleType}
options={[
{ label: 'Current Post', value: 'post' },
{ label: 'Current User', value: 'user' },
{ label: 'Current User Device', value: 'user_device' },
]}
onChange={setRuleType}
/>
<SelectControl
value={lockType}
options={
ruleType === 'post' ? postLockTypeOptions : ruleType === 'user' ? userLockTypeOptions : userDeviceLockTypeOptions
}
onChange={setLockType}
/>
{lockType === 'meta_value' && (
<>
<TextControl
value={metaKey}
onChange={setMetaKey}
placeholder="Meta Key"
/>
<SelectControl
value={metaKeyOperator}
options={comparisonOperators}
onChange={setMetaKeyOperator}
/>
<TextControl
value={metaValue}
onChange={setMetaValue}
placeholder="Meta Value"
/>
</>
)}
{lockType === 'has_role' && (
<SelectControl
value={metaKey}
options={roles}
onChange={setMetaKey}
/>
)}
{(lockType === 'in_term' || lockType === 'not_in_term') && (
<>
<SelectControl
value={metaKey}
options={taxonomies}
onChange={setMetaKey}
/>
<SelectControl
value={metaKey}
options={terms}
onChange={setMetaKey}
/>
</>
)}
{lockType === 'post_content_length' && (
<>
<SelectControl
value={postContentLengthOperator}
options={comparisonOperators}
onChange={setPostContentLengthOperator}
/>
<NumberControl
value={postContentLengthAmount}
onChange={setPostContentLengthAmount}
/>
<SelectControl
value={postContentLengthUnit}
options={[
{ label: 'Characters', value: 'characters' },
{ label: 'Words', value: 'words' } ]}
onChange={setPostContentLengthUnit}
/>
</>
)}
{lockType === 'post_date' && (
<>
<SelectControl
value={postDateOperator}
options={comparisonOperators}
onChange={setPostDateOperator}
/>
<DateTimePicker
value={postDate}
onChange={setPostDate}
/>
</>
)}
</div>
{children}
</div>
);
};
export default DraggableItem;

View File

@ -0,0 +1,120 @@
import { useDrop } from 'react-dnd';
import { Button, ToggleControl, __experimentalNumberControl as NumberControl, SelectControl } from '@wordpress/components';
import { useState, useEffect, useCallback } from '@wordpress/element';
import DraggableItem from './DraggableItem';
const PostTypeContainer = ({ postType }) => {
const [rules, setRules] = useState([{ id: Date.now() }]);
const [lockDefault, setLockDefault] = useState(false);
const [enableMeter, setEnableMeter] = useState(false);
const [meterAmount, setMeterAmount] = useState(0);
const [meterDuration, setMeterDuration] = useState(1);
const [meterCookieUnits, setMeterCookieUnits] = useState('hour');
const [totalSeconds, setTotalSeconds] = useState(3600);
const [{ isOver }, drop] = useDrop(() => ({
accept: 'rule',
drop: () => ({ postType: postType.name }),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const moveItem = useCallback((dragIndex, hoverIndex) => {
setRules((prevRules) => {
const newRules = [...prevRules];
const dragItem = newRules[dragIndex];
// Remove the dragged item
newRules.splice(dragIndex, 1);
// Insert it at the new position
newRules.splice(hoverIndex, 0, dragItem);
return newRules;
});
}, []);
const addRule = () => {
setRules([...rules, { id: Date.now() }]);
};
const calculateTotalSeconds = (duration, unit) => {
const unitToSeconds = {
second: 1,
minute: 60,
hour: 3600,
day: 86400,
week: 604800,
};
return duration * (unitToSeconds[unit] || 0);
};
// Update total seconds whenever duration or unit changes
useEffect(() => {
setTotalSeconds(calculateTotalSeconds(meterDuration, meterCookieUnits));
}, [meterDuration, meterCookieUnits]);
return (
<div className={`lcp-paywall-post-type ${isOver ? 'is-over' : ''}`} ref={drop}>
<h3>{postType.label}</h3>
<ToggleControl
label="Lock Posts By Default"
checked={lockDefault}
onChange={setLockDefault}
/>
<ToggleControl
label="Enable Meter"
checked={enableMeter}
onChange={setEnableMeter}
/>
{enableMeter && (
<>
<NumberControl
label="Meter Amount"
value={meterAmount}
onChange={setMeterAmount}
/>
<NumberControl
label="Meter Cookie Duration"
value={meterDuration}
onChange={setMeterDuration}
/>
<SelectControl
label="Meter Cookie Units"
value={meterCookieUnits}
options={[
{ label: 'Minutes', value: 'minute' },
{ label: 'Hours', value: 'hour' },
{ label: 'Days', value: 'day' },
{ label: 'Weeks', value: 'week' },
]}
onChange={setMeterCookieUnits}
/>
<p><strong>Total Seconds:</strong> {totalSeconds}</p>
</>
)}
<div className="lcp-paywall-rules-container">
{rules.map((rule, index) => (
<DraggableItem
key={rule.id}
id={rule.id}
index={index}
moveItem={moveItem}
postType={postType.name}
/>
))}
</div>
<div className="lcp-paywall-add-rule">
<Button
variant="secondary"
onClick={addRule}
className="lcp-paywall-add-rule-button"
>
Add Rule
</Button>
</div>
</div>
);
};
export default PostTypeContainer;

24
src/index.js Normal file
View File

@ -0,0 +1,24 @@
import { render } from '@wordpress/element';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import PostTypeContainer from './components/PostTypeContainer';
const App = () => {
return (
<DndProvider backend={HTML5Backend}>
<div id="lcp-paywall-app">
{window.lcpPaywallData.postTypes.map(postType => (
<PostTypeContainer key={postType.name} postType={postType} />
))}
</div>
</DndProvider>
);
};
// Wait for DOM to be ready
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('lcp-paywall-app-container');
if (container) {
render(<App />, container);
}
});