22 * AppverseDataContext
33 *
44 * Provides global data for software and apps throughout the application.
5- * Fetches both endpoints on mount and makes data available via context .
5+ * Fetches from static JSON cache on mount .
66 *
77 * Usage:
88 * import { useAppverseData } from '../hooks/useAppverseData'
9- * const { software, apps, appsBySoftwareId, loading, error } = useAppverseData()
9+ * const { software, appsBySoftwareId, loading, error } = useAppverseData()
1010 */
1111import { createContext , useState , useEffect , useMemo , useCallback } from 'react' ;
12- import { fetchAllSoftware , fetchAllApps , fetchAllAppTypes , groupAppsBySoftware , extractFilterOptionsFromApps , extractFilterOptionsFromSoftware } from '../utils/api' ;
12+ import { fetchStaticCache } from '../utils/api' ;
1313import { slugify } from '../utils/slugify' ;
1414import { useConfig } from './ConfigContext' ;
1515
1616export const AppverseDataContext = createContext ( null ) ;
1717
18- /**
19- * @typedef {Object } SoftwareItem
20- * @property {string } id - UUID
21- * @property {string } type - "node--appverse_software"
22- * @property {Object } attributes
23- * @property {string } attributes.title - Software name
24- * @property {Object } attributes.body - {value: string, format: string, processed: string}
25- * @property {Object } attributes.field_appverse_software_doc - {uri: string, title: string}
26- * @property {Object } attributes.field_appverse_software_website - {uri: string, title: string}
27- * @property {number } attributes.drupal_internal__nid - Drupal node ID
28- * @property {string } attributes.created - ISO timestamp
29- * @property {string } attributes.changed - ISO timestamp
30- * @property {Object } relationships
31- * @property {Object|null } relationships.field_appverse_logo - Logo media (type: "media--svg")
32- * @property {Array } relationships.field_appverse_topics - Topics (taxonomy)
33- * @property {Object|null } relationships.field_license - License (taxonomy)
34- * @property {Array } relationships.field_tags - Tags (taxonomy)
35- * @property {Array } relationships.field_domain_access - Domains
36- * @property {string|null } logoUrl - Resolved logo URL (added by api.js)
37- */
38-
39- /**
40- * @typedef {Object } AppItem
41- * @property {string } id - UUID
42- * @property {string } type - "node--appverse_app"
43- * @property {Object } attributes
44- * @property {string } attributes.title - App name
45- * @property {Object } attributes.field_implementation_details - {value: string, format: string, processed: string}
46- * @property {number } attributes.drupal_internal__nid - Drupal node ID
47- * @property {string } attributes.created - ISO timestamp
48- * @property {string } attributes.changed - ISO timestamp
49- * @property {Object } relationships
50- * @property {Object } relationships.field_appverse_software_implemen - Software reference
51- * @property {Object|null } relationships.field_appverse_app_type - App type (taxonomy)
52- * @property {Array } relationships.field_add_implementation_tags - Tags (taxonomy)
53- * @property {Object|null } relationships.field_appverse_organization - Organization (taxonomy)
54- * @property {Object|null } relationships.field_license - License (taxonomy)
55- */
56-
57- /**
58- * @typedef {Object } AppverseData
59- * @property {SoftwareItem[] } software - All software items with logo URLs resolved
60- * @property {AppItem[] } apps - All app items
61- * @property {Object.<string, AppItem[]> } appsBySoftwareId - Apps grouped by software UUID
62- * @property {boolean } loading - Loading state
63- * @property {Error|null } error - Error object if fetch failed
64- * @property {Function } refetch - Function to manually refetch data
65- */
66-
6718export function AppverseDataProvider ( { children } ) {
6819 const config = useConfig ( ) ;
6920 const [ data , setData ] = useState ( {
7021 software : [ ] ,
71- apps : [ ] ,
7222 appsBySoftwareId : { } ,
7323 filterOptions : { tags : [ ] , appType : [ ] , topics : [ ] , license : [ ] } ,
74- softwareLoading : true ,
75- appsLoading : true ,
24+ loading : true ,
7625 error : null
7726 } ) ;
7827
79- /**
80- * Merge tags from two filter option sources, deduplicating by ID
81- */
82- const mergeTags = ( existingTags , newTags ) => {
83- const tagMap = { } ;
84- for ( const tag of existingTags ) tagMap [ tag . id ] = tag ;
85- for ( const tag of newTags ) tagMap [ tag . id ] = tag ;
86- return Object . values ( tagMap ) . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
87- } ;
88-
89- /**
90- * Fetch all data from API
91- * Software and apps are fetched concurrently but update state independently
92- * so the grid can render as soon as software arrives.
93- */
9428 const fetchData = ( ) => {
95- setData ( prev => ( { ...prev , softwareLoading : true , appsLoading : true , error : null } ) ) ;
29+ setData ( prev => ( { ...prev , loading : true , error : null } ) ) ;
9630
97- // Fetch software — update state as soon as it resolves
98- fetchAllSoftware ( config )
99- . then ( ( { software, included : softwareIncluded } ) => {
100- const softwareFilterOptions = extractFilterOptionsFromSoftware ( softwareIncluded ) ;
101- setData ( prev => ( {
102- ...prev ,
31+ fetchStaticCache ( config )
32+ . then ( ( { software, appsBySoftwareId, filterOptions } ) => {
33+ setData ( {
10334 software,
104- softwareLoading : false ,
105- filterOptions : {
106- ...prev . filterOptions ,
107- topics : softwareFilterOptions . topics ,
108- license : softwareFilterOptions . license ,
109- tags : mergeTags ( prev . filterOptions . tags , softwareFilterOptions . tags )
110- }
111- } ) ) ;
112- } )
113- . catch ( error => {
114- console . error ( 'Failed to fetch software:' , error ) ;
115- setData ( prev => ( { ...prev , softwareLoading : false , error } ) ) ;
116- } ) ;
117-
118- // Fetch apps and all app types concurrently
119- Promise . all ( [ fetchAllApps ( config ) , fetchAllAppTypes ( config ) ] )
120- . then ( ( [ { apps, included : appsIncluded } , allAppTypes ] ) => {
121- const appsFilterOptions = extractFilterOptionsFromApps ( appsIncluded ) ;
122- const appsBySoftwareId = groupAppsBySoftware ( apps ) ;
123- setData ( prev => ( {
124- ...prev ,
125- apps,
12635 appsBySoftwareId,
127- appsLoading : false ,
128- filterOptions : {
129- ...prev . filterOptions ,
130- appType : allAppTypes ,
131- tags : mergeTags ( prev . filterOptions . tags , appsFilterOptions . tags )
132- }
133- } ) ) ;
36+ filterOptions,
37+ loading : false ,
38+ error : null ,
39+ } ) ;
13440 } )
13541 . catch ( error => {
136- console . error ( 'Failed to fetch apps :' , error ) ;
137- setData ( prev => ( { ...prev , appsLoading : false , error } ) ) ;
42+ console . error ( 'Failed to fetch appverse data :' , error ) ;
43+ setData ( prev => ( { ...prev , loading : false , error } ) ) ;
13844 } ) ;
13945 } ;
14046
@@ -144,31 +50,25 @@ export function AppverseDataProvider({ children }) {
14450 } , [ ] ) ;
14551
14652 // Build slug map: slugify(title) → software object
147- // This allows /appverse/abaqus to resolve to the correct software
14853 const slugMap = useMemo ( ( ) => {
14954 const map = { } ;
15055 for ( const sw of data . software ) {
151- const title = sw . attributes ?. title ;
152- if ( title ) {
153- const slug = slugify ( title ) ;
154- map [ slug ] = sw ;
56+ if ( sw . title ) {
57+ map [ slugify ( sw . title ) ] = sw ;
15558 }
15659 }
15760 return map ;
15861 } , [ data . software ] ) ;
15962
160- // Helper to get software by slug (memoized to prevent useEffect re-triggers)
16163 const getSoftwareBySlug = useCallback ( ( slug ) => {
16264 return slugMap [ slug ] || null ;
16365 } , [ slugMap ] ) ;
16466
16567 const contextValue = {
16668 ...data ,
167- // Backward-compatible loading: true only while software is loading (grid not yet visible)
168- loading : data . softwareLoading ,
16969 slugMap,
17070 getSoftwareBySlug,
171- refetch : fetchData // Allow manual refetch if needed
71+ refetch : fetchData ,
17272 } ;
17373
17474 return (
0 commit comments