/home/techb158/primomovers.ca/wp-content/plugins/everest-forms/src/templates/components
Edit: /home/techb158/primomovers.ca/wp-content/plugins/everest-forms/src/templates/components/Main.tsx (17458B)
import {
Box,
Flex,
Heading,
Icon,
Input,
InputGroup,
InputLeftElement,
keyframes,
Tab,
TabList,
Tabs,
Text,
useToast,
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import debounce from 'lodash.debounce';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { IoSearchOutline } from 'react-icons/io5';
import { FiRefreshCw } from 'react-icons/fi';
import { templatesScriptData } from '../utils/global';
import Sidebar from './Sidebar';
import TemplateList from './TemplateList';
import CreateFormCTA from './CreateFormCTA';
const { restURL, security } = templatesScriptData;
const fetchTemplates = async () => {
const response = (await apiFetch({
path: `${restURL}everest-forms/v1/templates`,
method: 'GET',
headers: {
'X-WP-Nonce': security,
},
})) as { templates: { category: string; templates: Template[] }[] };
if (response && Array.isArray(response.templates)) {
const allTemplates = response.templates.flatMap(
(category) => category.templates,
);
return allTemplates;
} else {
throw new Error(__('Unexpected response format.', 'everest-forms'));
}
};
interface CreateFormResponse {
success: boolean;
data?: { id: number; redirect: string; status: number };
message?: string;
}
const shimmer = keyframes`
0% { background-position: -600px 0; }
100% { background-position: 600px 0; }
`;
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
const skimmerStyle = {
background: 'linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%)',
backgroundSize: '600px 100%',
animation: `${shimmer} 1.6s ease-in-out infinite`,
borderRadius: '4px',
};
const SkelBox: React.FC<{ w?: string; h?: string; mb?: string; br?: string }> = ({ w = '100%', h = '14px', mb = '0', br = '4px' }) => (
);
// Template card skeleton — matches actual card: gradient image bg + white inner wrapper + info section
const TemplateCardSkeleton = () => (
{/* Image area: gradient bg + white inner card (matching actual card structure) */}
{/* Info section */}
);
const TemplateSkeleton = () => (
{/* CTA cards — 2-col equal-width grid, matching CreateFormCTA layout */}
{/* AI card: icon + badge row, then title/desc/link */}
{/* Scratch card: standalone icon, then title/desc/link */}
{/* Template section card */}
{/* Top bar: search (left, w-256px) | heading + filter tabs (right) */}
{/* Search input shimmer */}
{/* "Choose from Templates" heading */}
{/* Filter tabs pill */}
{/* Sidebar + template grid */}
{/* Sidebar: CATEGORIES label + rows + Can't find card */}
{/* "CATEGORIES" label */}
{/* Category rows */}
{[78, 62, 88, 55, 72, 60, 70, 52, 65, 58].map((w, i) => (
))}
{/* "Can't find a template?" card */}
{/* Template grid: 2-col (xl: 3-col) matching TemplateList */}
{Array.from({ length: 6 }).map((_, i) => (
))}
);
const Main: React.FC<{ onCreateWithAI?: (formId?: number, title?: string) => void }> = ({ onCreateWithAI }) => {
const toast = useToast();
const [filter, setFilter] = useState(__('All', 'everest-forms'));
const [isCreatingBlank, setIsCreatingBlank] = useState(false);
const [searchInputValue, setSearchInputValue] = useState('');
const [state, setState] = useState({
selectedCategory: __('All Forms', 'everest-forms'),
searchTerm: '',
});
const [categorySetFromURL, setCategorySetFromURL] = useState(false);
const { selectedCategory, searchTerm } = state;
const {
data: templates = [],
isLoading,
isFetching,
refetch,
error,
} = useQuery(['templates'], fetchTemplates);
const categories = useMemo(() => {
const categoriesSet = new Set
();
templates.forEach((template) => {
template.categories.forEach((category) => categoriesSet.add(category));
});
return [
{ name: __('All Forms', 'everest-forms'), count: templates.length },
...Array.from(categoriesSet).map((category) => ({
name: category,
count: templates.filter((template) =>
template.categories.includes(category),
).length,
})),
];
}, [templates]);
useEffect(() => {
if (categorySetFromURL) return;
if (categories.length <= 1) return;
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('evf_template_category')) {
const categorySlug = urlParams.get('evf_template_category') || '';
const normalize = (str: string) =>
str
.toLowerCase()
.replace(/\s+/g, '')
.replace(/[^a-z0-9]/g, '');
const normalizedSlug = normalize(categorySlug);
let matchedCategory = categories.find((cat) => {
const normalizedCatName = normalize(cat.name);
if (normalizedCatName === normalizedSlug) return true;
if (normalizedCatName.startsWith(normalizedSlug)) return true;
if (normalizedSlug.startsWith(normalizedCatName)) return true;
return false;
});
if (!matchedCategory) {
matchedCategory = categories.find((cat) => {
const normalizedCatName = normalize(cat.name);
const slugWords = categorySlug.toLowerCase().split(/[\s-]+/);
const catWords = cat.name.toLowerCase().split(/[\s-]+/);
const hasMatchingWord = slugWords.some((word) =>
catWords.some(
(catWord) => catWord.includes(word) || word.includes(catWord),
),
);
if (hasMatchingWord) return true;
if (
normalizedCatName.includes(normalizedSlug) ||
normalizedSlug.includes(normalizedCatName)
) return true;
return false;
});
}
if (
matchedCategory &&
matchedCategory.name !== __('All Forms', 'everest-forms')
) {
setState((prevState) => ({
...prevState,
selectedCategory: matchedCategory.name,
}));
setCategorySetFromURL(true);
}
}
}, [categories, categorySetFromURL]);
const filteredTemplates = useMemo(() => {
return templates.filter(
(template) =>
template.slug !== 'blank' &&
(selectedCategory === __('All Forms', 'everest-forms') ||
template.categories.includes(selectedCategory)) &&
template.title.toLowerCase().includes(searchTerm.toLowerCase()) &&
(filter === __('All', 'everest-forms') ||
(filter === __('Free', 'everest-forms') && !template.isPro) ||
(filter === __('Premium', 'everest-forms') && template.isPro)),
);
}, [selectedCategory, searchTerm, templates, filter]);
const handleCategorySelect = useCallback((category: string) => {
setState((prevState) => ({ ...prevState, selectedCategory: category }));
}, []);
const debouncedSetSearch = useCallback(
debounce((value: string) => {
setState((prevState) => ({ ...prevState, searchTerm: value }));
}, 300),
[],
);
const handleSearchInputChange = (e: React.ChangeEvent) => {
const value = e.target.value;
setSearchInputValue(value);
debouncedSetSearch(value);
};
if (isLoading) return ;
if (error) return {(error as Error).message}
;
const handleCreateWithAI = (formId?: number, title?: string) => {
if (onCreateWithAI) onCreateWithAI(formId, title);
};
const handleCreateBlank = async () => {
setIsCreatingBlank(true);
try {
const response = (await apiFetch({
path: `${restURL}everest-forms/v1/templates/create`,
method: 'POST',
body: JSON.stringify({
title: __('Untitled', 'everest-forms'),
slug: 'blank',
}),
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': security,
},
})) as CreateFormResponse;
if (response.success && response.data) {
window.location.href = response.data.redirect;
} else {
setIsCreatingBlank(false);
toast({
title: __('Error', 'everest-forms'),
description: response.message || __('Failed to create form.', 'everest-forms'),
status: 'error',
position: 'bottom-right',
duration: 5000,
isClosable: true,
variant: 'subtle',
});
}
} catch (error) {
setIsCreatingBlank(false);
toast({
title: __('Error', 'everest-forms'),
description: __('An error occurred while creating the form.', 'everest-forms'),
status: 'error',
position: 'bottom-right',
duration: 5000,
isClosable: true,
variant: 'subtle',
});
}
};
const filterLabels = [
__('All', 'everest-forms'),
__('Free', 'everest-forms'),
__('Premium', 'everest-forms'),
];
return (
{/* CTA Cards — 2-column equal-width grid */}
{/* Template Section Card */}
{/* Subtle refetch indicator — thin animated bar at top */}
{isFetching && !isLoading && (
)}
{/* Top bar: search (left) | heading + filter tabs (right) */}
{/* Search area — aligned with sidebar width */}
{/* Heading + filter tabs */}
{__('Choose from Templates', 'everest-forms')}
{/* Refetch button + Filter tabs */}
{ if (!isFetching) refetch(); }}
_hover={{ borderColor: '#7545BB', color: '#7545BB' }}
transition="all 0.15s"
title={__('Refresh templates', 'everest-forms')}
>
{__('Refetch', 'everest-forms')}
setFilter(filterLabels[index])}
>
{filterLabels.map((label) => (
{label}
))}
{/* Sidebar + Template Grid */}
{/* Sidebar */}
{/* Template grid */}
);
};
export default Main;