From 235ce5716edc2f7cbe80fdccf26eac7269587839 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 08 Jun 2026 04:38:18 +0000
Subject: [PATCH] =FavouritesManager.php and FavouritesRoutes.php fixes. Moving all logic to FavouritesManager.php. Still some left to do

---
 src/feed/edit.js |  256 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 256 insertions(+), 0 deletions(-)

diff --git a/src/feed/edit.js b/src/feed/edit.js
new file mode 100644
index 0000000..2a5228d
--- /dev/null
+++ b/src/feed/edit.js
@@ -0,0 +1,256 @@
+/**
+ * Feed Block - Edit Component
+ * Fetches available feed types from /jvb/v1/feed/types
+ * Allows configuration of content types and inherit query setting
+ */
+
+import { useEffect, useState } from '@wordpress/element';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import {
+	PanelBody,
+	CheckboxControl,
+	ToggleControl,
+	Spinner,
+	Notice
+} from '@wordpress/components';
+import apiFetch from '@wordpress/api-fetch';
+import { __ } from '@wordpress/i18n';
+
+export default function Edit({ attributes, setAttributes }) {
+	const [feedTypes, setFeedTypes] = useState(null);
+	const [loading, setLoading] = useState(true);
+	const [error, setError] = useState(null);
+
+	const blockProps = useBlockProps({
+		className: 'feed-block-editor'
+	});
+
+	/**
+	 * Fetch available feed types on component mount
+	 */
+	useEffect(() => {
+		apiFetch({
+			path: '/jvb/v1/feed/types',
+			headers: {
+				'If-Modified-Since': localStorage.getItem('feed_types_modified'),
+			}
+		})
+			.then(types => {
+				setFeedTypes(types);
+				setLoading(false);
+
+				// Store Last-Modified for future requests
+				// (apiFetch doesn't expose response headers easily,
+				// but the server will handle 304s)
+
+				// Initialize contentTypes if not set and not inheriting
+				if (!attributes.contentTypes && !attributes.inheritQuery) {
+					const firstType = Object.keys(types)[0];
+					if (firstType) {
+						setAttributes({ contentTypes: [firstType] });
+					}
+				}
+			})
+			.catch(err => {
+				console.error('Error loading feed types:', err);
+				setError(err.message);
+				setLoading(false);
+			});
+	}, [attributes.inheritQuery]);
+
+	/**
+	 * Toggle a content type in the selection
+	 */
+	const toggleContentType = (slug, checked) => {
+		const currentTypes = attributes.contentTypes || [];
+
+		const newTypes = checked
+			? [...currentTypes, slug]
+			: currentTypes.filter(t => t !== slug);
+
+		setAttributes({ contentTypes: newTypes });
+	};
+
+	/**
+	 * Get friendly label for content type
+	 */
+	const getTypeLabel = (slug, config) => {
+		return `${config.plural} (${config.type})`;
+	};
+
+	/**
+	 * Group types by category for better UX
+	 */
+	const groupedTypes = feedTypes ? {
+		content: Object.entries(feedTypes)
+			.filter(([_, config]) => config.type === 'content'),
+		taxonomy: Object.entries(feedTypes)
+			.filter(([_, config]) => config.type === 'taxonomy')
+	} : { content: [], taxonomy: [] };
+
+	return (
+		<div {...blockProps}>
+			<InspectorControls>
+				<PanelBody
+					title={__('Feed Settings', 'jvb')}
+					initialOpen={true}
+				>
+					<ToggleControl
+						label={__('Inherit from Page Context', 'jvb')}
+						help={
+							attributes.inheritQuery
+								? __('Feed will adapt to the current page (profile, taxonomy, etc.)', 'jvb')
+								: __('Manually select content types to display', 'jvb')
+						}
+						checked={attributes.inheritQuery}
+						onChange={(value) => setAttributes({ inheritQuery: value })}
+					/>
+
+					{!attributes.inheritQuery && (
+						<>
+							{loading && (
+								<div style={{ textAlign: 'center', padding: '20px' }}>
+									<Spinner />
+									<p>{__('Loading feed types...', 'jvb')}</p>
+								</div>
+							)}
+
+							{error && (
+								<Notice status="error" isDismissible={false}>
+									{__('Error loading feed types: ', 'jvb')} {error}
+								</Notice>
+							)}
+
+							{!loading && !error && feedTypes && (
+								<>
+									{groupedTypes.content.length > 0 && (
+										<>
+											<h4>{__('Content Types', 'jvb')}</h4>
+											{groupedTypes.content.map(([slug, config]) => (
+												<CheckboxControl
+													key={slug}
+													label={getTypeLabel(slug, config)}
+													checked={
+														attributes.contentTypes?.includes(slug) || false
+													}
+													onChange={(checked) =>
+														toggleContentType(slug, checked)
+													}
+													help={
+														config.taxonomies?.length > 0
+															? `Filters: ${config.taxonomies.join(', ')}`
+															: null
+													}
+												/>
+											))}
+										</>
+									)}
+
+									{groupedTypes.taxonomy.length > 0 && (
+										<>
+											<h4 style={{ marginTop: '20px' }}>
+												{__('Content Taxonomies', 'jvb')}
+											</h4>
+											<p style={{ fontSize: '12px', color: '#757575' }}>
+												{__('These are collections that group other content', 'jvb')}
+											</p>
+											{groupedTypes.taxonomy.map(([slug, config]) => (
+												<CheckboxControl
+													key={slug}
+													label={getTypeLabel(slug, config)}
+													checked={
+														attributes.contentTypes?.includes(slug) || false
+													}
+													onChange={(checked) =>
+														toggleContentType(slug, checked)
+													}
+													help={
+														config.for_content?.length > 0
+															? `Contains: ${config.for_content.join(', ')}`
+															: null
+													}
+												/>
+											))}
+										</>
+									)}
+
+									{!attributes.contentTypes?.length && (
+										<Notice status="warning" isDismissible={false}>
+											{__('Please select at least one content type', 'jvb')}
+										</Notice>
+									)}
+								</>
+							)}
+						</>
+					)}
+				</PanelBody>
+
+				<PanelBody
+					title={__('Display Settings', 'jvb')}
+					initialOpen={false}
+				>
+					<ToggleControl
+						label={__('Show Gallery View', 'jvb')}
+						help={__('Enable lightbox for images', 'jvb')}
+						checked={attributes.enableGallery || false}
+						onChange={(value) =>
+							setAttributes({ enableGallery: value })
+						}
+					/>
+				</PanelBody>
+			</InspectorControls>
+
+			<div className="feed-block-placeholder">
+				<div className="feed-block-icon">
+					<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
+						<rect x="3" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="13" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="3" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="13" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
+					</svg>
+				</div>
+
+				<h3>{__('Feed Block', 'jvb')}</h3>
+
+				{attributes.inheritQuery ? (
+					<p className="feed-block-description">
+						{__('📍 Inheriting from page context', 'jvb')}
+					</p>
+				) : (
+					<div className="feed-block-description">
+						{attributes.contentTypes?.length > 0 ? (
+							<>
+								<p><strong>{__('Showing:', 'jvb')}</strong></p>
+								<ul style={{
+									listStyle: 'none',
+									padding: '0',
+									margin: '8px 0'
+								}}>
+									{attributes.contentTypes.map(type => {
+										const config = feedTypes?.[type];
+										return (
+											<li key={type} style={{
+												padding: '4px 0',
+												color: '#2271b1'
+											}}>
+												✓ {config?.plural || type}
+											</li>
+										);
+									})}
+								</ul>
+							</>
+						) : (
+							<p style={{ color: '#d63638' }}>
+								{__('⚠️  No content types selected', 'jvb')}
+							</p>
+						)}
+					</div>
+				)}
+
+				<p className="feed-block-note">
+					{__('Feed will be displayed on the frontend', 'jvb')}
+				</p>
+			</div>
+		</div>
+	);
+}

--
Gitblit v1.10.0