@@ -8,6 +8,11 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
88
99import styles from './index.module.css' ;
1010
11+ // Import centralized product configuration
12+ import reposConfig from '../../repos-config.json' ;
13+
14+ const { categories, products } = reposConfig ;
15+
1116/**
1217 * Build an absolute URL using the site config base URL.
1318 * @param {string } siteUrl
@@ -26,6 +31,72 @@ function buildAbsoluteUrl(siteUrl, baseUrl, pathname) {
2631 return `${ origin } ${ baseUrlWithSlash } ${ normalizedPath } ` ;
2732}
2833
34+ /**
35+ * Get products by category ID, sorted alphabetically by label.
36+ * @param {string } categoryId
37+ * @param {Object } options
38+ * @param {string } options.showFirst - Product ID to show first
39+ * @param {boolean } options.excludeThirdParty - Exclude third-party products
40+ * @returns {Array }
41+ */
42+ function getProductsByCategory ( categoryId , options = { } ) {
43+ const { showFirst, excludeThirdParty = false } = options ;
44+
45+ let filtered = products . filter ( ( p ) => {
46+ if ( p . category !== categoryId ) return false ;
47+ if ( excludeThirdParty && p . isThirdParty ) return false ;
48+ return true ;
49+ } ) ;
50+
51+ // Sort alphabetically
52+ filtered . sort ( ( a , b ) => a . label . localeCompare ( b . label ) ) ;
53+
54+ // Move showFirst product to the beginning
55+ if ( showFirst ) {
56+ const firstIndex = filtered . findIndex ( ( p ) => p . id === showFirst ) ;
57+ if ( firstIndex > 0 ) {
58+ const [ first ] = filtered . splice ( firstIndex , 1 ) ;
59+ filtered . unshift ( first ) ;
60+ }
61+ }
62+
63+ return filtered . map ( ( p ) => ( {
64+ id : p . id ,
65+ title : p . label ,
66+ description : p . description || '' ,
67+ link : `/docs/${ p . id } /` ,
68+ } ) ) ;
69+ }
70+
71+ /**
72+ * Get third-party products (isThirdParty: true).
73+ * @returns {Array }
74+ */
75+ function getThirdPartyProducts ( ) {
76+ return products
77+ . filter ( ( p ) => p . isThirdParty === true )
78+ . sort ( ( a , b ) => a . label . localeCompare ( b . label ) )
79+ . map ( ( p ) => ( {
80+ id : p . id ,
81+ title : p . label ,
82+ description : p . description || '' ,
83+ link : `/docs/${ p . id } /` ,
84+ } ) ) ;
85+ }
86+
87+ /**
88+ * Get sorted categories for display.
89+ * @returns {Array }
90+ */
91+ function getSortedCategories ( ) {
92+ return Object . entries ( categories )
93+ . map ( ( [ id , config ] ) => ( {
94+ id,
95+ ...config ,
96+ } ) )
97+ . sort ( ( a , b ) => ( a . position || 99 ) - ( b . position || 99 ) ) ;
98+ }
99+
29100/**
30101 * Render the homepage hero header.
31102 * @returns {JSX.Element }
@@ -44,187 +115,25 @@ function HomepageHeader() {
44115 ) ;
45116}
46117
47- // Main GravityKit Products
48- const mainProducts = [
49- {
50- title : 'GravityView' ,
51- description : 'Display Gravity Forms entries in beautiful, customizable layouts.' ,
52- link : '/docs/gravityview/'
53- } ,
54- {
55- title : 'GravityCalendar' ,
56- description : 'Transform entries into interactive calendars with FullCalendar.' ,
57- link : '/docs/gravitycalendar/'
58- } ,
59- {
60- title : 'GravityCharts' ,
61- description : 'Visualize form data with powerful charts and graphs.' ,
62- link : '/docs/gravitycharts/'
63- } ,
64- {
65- title : 'GravityImport' ,
66- description : 'Import data into Gravity Forms from CSV, Excel, and more.' ,
67- link : '/docs/gravityimport/'
68- } ,
69- {
70- title : 'GravityExport' ,
71- description : 'Export form entries in multiple formats.' ,
72- link : '/docs/gravityexport/'
73- } ,
74- {
75- title : 'GravityMath' ,
76- description : 'Advanced mathematical calculations for your forms.' ,
77- link : '/docs/gravitymath/'
78- } ,
79- {
80- title : 'GravityEdit' ,
81- description : 'Edit Gravity Forms entries inline to save time and streamline your workflow.' ,
82- link : '/docs/gravityedit/'
83- } ,
84- {
85- title : 'GravityActions' ,
86- description : 'Update multiple entries at once, send bulk emails, and automate workflows.' ,
87- link : '/docs/gravityactions/'
88- } ,
89- {
90- title : 'GravityRevisions' ,
91- description : 'Track, compare, and restore changes made to forms and entries.' ,
92- link : '/docs/gravityrevisions/'
93- } ,
94- {
95- title : 'GravityMigrate' ,
96- description : 'Migrate all Gravity Forms data including forms, entries, Views, and feeds.' ,
97- link : '/docs/gravitymigrate/'
98- } ,
99- {
100- title : 'GravityBoard' ,
101- description : 'Manage projects with collaborative Kanban-style project boards.' ,
102- link : '/docs/gravityboard/'
103- }
104- ] ;
105-
106- // GravityView Layouts
107- const gravityviewLayouts = [
108- {
109- title : 'DataTables' ,
110- description : 'Enhance Views with sortable, searchable DataTables.' ,
111- link : '/docs/gravityview-datatables/' ,
112- slug : 'gravityview-datatables'
113- } ,
114- {
115- title : 'DIY Layout' ,
116- description : 'Build custom layouts with complete control over HTML and CSS.' ,
117- link : '/docs/gravityview-diy-layout/' ,
118- slug : 'gravityview-diy-layout'
119- } ,
120- {
121- title : 'Maps' ,
122- description : 'Display entries on interactive Google Maps.' ,
123- link : '/docs/gravityview-maps/' ,
124- slug : 'gravityview-maps'
125- }
126- ] ;
127-
128- // GravityView Extensions
129- const gravityviewExtensions = [
130- {
131- title : 'Advanced Filtering' ,
132- description : 'Add powerful filtering capabilities to your Views.' ,
133- link : '/docs/gravityview-advanced-filtering/' ,
134- slug : 'gravityview-advanced-filtering'
135- } ,
136- {
137- title : 'A-Z Filters' ,
138- description : 'Filter entries alphabetically with letter-based navigation.' ,
139- link : '/docs/gravityview-az-filters/' ,
140- slug : 'gravityview-az-filters'
141- } ,
142- {
143- title : 'Dashboard Views' ,
144- description : 'Display Views in the WordPress admin dashboard.' ,
145- link : '/docs/gravityview-dashboard-views/' ,
146- slug : 'gravityview-dashboard-views'
147- } ,
148- {
149- title : 'Featured Entries' ,
150- description : 'Highlight and pin important entries to the top of Views.' ,
151- link : '/docs/gravityview-featured-entries/' ,
152- slug : 'gravityview-featured-entries'
153- } ,
154- {
155- title : 'Magic Links' ,
156- description : 'Share unique links for accessing entries without logging in.' ,
157- link : '/docs/gravityview-magic-links/' ,
158- slug : 'gravityview-magic-links'
159- } ,
160- {
161- title : 'Multiple Forms' ,
162- description : 'Combine entries from multiple forms into a single View.' ,
163- link : '/docs/gravityview-multiple-forms/' ,
164- slug : 'gravityview-multiple-forms'
165- } ,
166- {
167- title : 'Ratings & Reviews' ,
168- description : 'Add star ratings and reviews to your entries.' ,
169- link : '/docs/gravityview-ratings-reviews/' ,
170- slug : 'gravityview-ratings-reviews'
171- } ,
172- {
173- title : 'Social Sharing & SEO' ,
174- description : 'Enable social sharing and optimize entries for search engines.' ,
175- link : '/docs/gravityview-social-sharing-seo/' ,
176- slug : 'gravityview-social-sharing-seo'
177- }
178- ] ;
179-
180- // Free Gravity Forms Add-ons
181- const gravityFormsAddons = [
182- {
183- title : 'Zero Spam' ,
184- description : 'Block spam submissions without CAPTCHAs or honeypots.' ,
185- link : '/docs/gravity-forms-zero-spam/' ,
186- slug : 'gravity-forms-zero-spam'
187- } ,
188- {
189- title : 'Dynamic Lookup' ,
190- description : 'Dynamically populate fields from other forms or entries.' ,
191- link : '/docs/gravity-forms-dynamic-lookup/' ,
192- slug : 'gravity-forms-dynamic-lookup'
193- } ,
194- {
195- title : 'Entry Tags' ,
196- description : 'Organize entries with customizable tags.' ,
197- link : '/docs/gravity-forms-entry-tags/' ,
198- slug : 'gravity-forms-entry-tags'
199- } ,
200- {
201- title : 'Event Field' ,
202- description : 'Add event scheduling fields with date, time, and recurrence.' ,
203- link : '/docs/gravity-forms-event-field/' ,
204- slug : 'gravity-forms-event-field'
205- } ,
206- {
207- title : 'Elementor Widget' ,
208- description : 'Embed Gravity Forms in Elementor with a native widget.' ,
209- link : '/docs/gravity-forms-elementor-widget/' ,
210- slug : 'gravity-forms-elementor-widget'
211- }
212- ] ;
213-
214118/**
215119 * Render a product card.
216- * @param {{product: {title : string, description : string, link : string, slug? : string}, showImage?: boolean} } props
120+ * @param {{product: {id : string, title : string, description : string, link : string}, showImage?: boolean} } props
217121 * @returns {JSX.Element }
218122 */
219123function ProductCard ( { product, showImage = true } ) {
220- const imageName = product . slug || product . title . toLowerCase ( ) ;
124+ const imagePath = useBaseUrl ( `/img/${ product . id } .svg` ) ;
125+
221126 return (
222127 < div className = { clsx ( 'col col--4 margin-bottom--lg' ) } >
223128 < div className = "card" >
224129 < div className = "card__header" >
225130 { showImage && (
226131 < Link to = { product . link } >
227- < img src = { useBaseUrl ( `/img/${ imageName } .svg` ) } alt = { product . title } />
132+ < img
133+ src = { imagePath }
134+ alt = { product . title }
135+ onError = { ( e ) => { e . target . style . display = 'none' ; } }
136+ />
228137 </ Link >
229138 ) }
230139 < Link to = { product . link } >
@@ -248,10 +157,14 @@ function ProductCard({ product, showImage = true }) {
248157
249158/**
250159 * Render a section of product cards.
251- * @param {{title: string, description: string, products: Array<{title: string, description: string, link: string, slug?: string}> , showImages?: boolean} } props
252- * @returns {JSX.Element }
160+ * @param {{title: string, description: string, products: Array, showImages?: boolean} } props
161+ * @returns {JSX.Element|null }
253162 */
254163function ProductSection ( { title, description, products, showImages = true } ) {
164+ if ( ! products || products . length === 0 ) {
165+ return null ;
166+ }
167+
255168 return (
256169 < section className = { styles . products } >
257170 < div className = "container" >
@@ -260,8 +173,8 @@ function ProductSection({ title, description, products, showImages = true }) {
260173 { description && < p className = "hero__subtitle" > { description } </ p > }
261174 </ div >
262175 < div className = "row" >
263- { products . map ( ( product , idx ) => (
264- < ProductCard key = { idx } product = { product } showImage = { showImages } />
176+ { products . map ( ( product ) => (
177+ < ProductCard key = { product . id } product = { product } showImage = { showImages } />
265178 ) ) }
266179 </ div >
267180 </ div >
@@ -275,7 +188,8 @@ function ProductSection({ title, description, products, showImages = true }) {
275188 */
276189export default function Home ( ) {
277190 const { siteConfig} = useDocusaurusContext ( ) ;
278- const totalProducts = mainProducts . length + gravityviewLayouts . length + gravityviewExtensions . length + gravityFormsAddons . length ;
191+ const sortedCategories = getSortedCategories ( ) ;
192+ const thirdPartyProducts = getThirdPartyProducts ( ) ;
279193 const llmsHref = buildAbsoluteUrl ( siteConfig . url , siteConfig . baseUrl , 'llms.txt' ) ;
280194
281195 return (
@@ -287,38 +201,50 @@ export default function Home() {
287201 </ Head >
288202 < HomepageHeader />
289203 < main >
290- < ProductSection
291- title = "Main Products"
292- description = "Core GravityKit products with comprehensive functionality"
293- products = { mainProducts }
294- showImages = { true }
295- />
204+ { sortedCategories . map ( ( category ) => {
205+ // Special handling for gravitykit category: include GravityView first
206+ const options = category . id === 'gravitykit'
207+ ? { showFirst : 'gravityview' }
208+ : { excludeThirdParty : true } ;
296209
297- < ProductSection
298- title = "GravityView Layouts"
299- description = "Alternative ways to display your View data"
300- products = { gravityviewLayouts }
301- showImages = { false }
302- />
210+ // For gravitykit, also include gravityview at the end
211+ let categoryProducts ;
212+ if ( category . id === 'gravitykit' ) {
213+ const gravityview = products . find ( ( p ) => p . id === 'gravityview' ) ;
214+ const gravityKitProducts = getProductsByCategory ( 'gravitykit' ) ;
215+ categoryProducts = gravityview
216+ ? [ ...gravityKitProducts , { id : gravityview . id , title : gravityview . label , description : gravityview . description , link : `/docs/${ gravityview . id } /` } ]
217+ : gravityKitProducts ;
218+ } else if ( category . id === 'gravityview' ) {
219+ // Skip gravityview category since it's included in gravitykit
220+ return null ;
221+ } else {
222+ categoryProducts = getProductsByCategory ( category . id , options ) ;
223+ }
303224
304- < ProductSection
305- title = "GravityView Extensions"
306- description = "Add features and functionality to GravityView"
307- products = { gravityviewExtensions }
308- showImages = { false }
309- />
225+ return (
226+ < ProductSection
227+ key = { category . id }
228+ title = { category . label }
229+ description = { category . description }
230+ products = { categoryProducts }
231+ showImages = { true }
232+ />
233+ ) ;
234+ } ) }
310235
236+ { /* Third Party Section */ }
311237 < ProductSection
312- title = "Gravity Forms Add-ons "
313- description = "Free plugins that enhance Gravity Forms "
314- products = { gravityFormsAddons }
315- showImages = { false }
238+ title = "Third Party "
239+ description = "Documentation for third-party plugins. "
240+ products = { thirdPartyProducts }
241+ showImages = { true }
316242 />
317243
318244 < div className = "container" >
319245 < div className = "text--center margin-top--lg margin-bottom--lg" >
320246 < p >
321- < strong > { totalProducts } products</ strong > with comprehensive hook documentation
247+ < strong > { products . length } products</ strong > with comprehensive hook documentation
322248 </ p >
323249 < p >
324250 Looking for user documentation? Visit the{ ' ' }
0 commit comments