Skip to content

Commit

Permalink
Merge pull request #272 from carverauto/updates/missing_jnext_files
Browse files Browse the repository at this point in the history
Updates/missing jnext files
  • Loading branch information
mfreeman451 authored Mar 2, 2025
2 parents 05a5c4a + 099daef commit 94b83ff
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 1 deletion.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ jspm_packages/
# TernJS port file
.tern-port

lib
.idea
.bin
bin
Expand Down
11 changes: 11 additions & 0 deletions web/src/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// src/env.js
import { createRuntimeEnv } from 'next-runtime-env';

// Create environment with server runtime variables
export const env = createRuntimeEnv({
// List the server-only variables you need access to
serverOnly: ['API_KEY'],

// Optional: List any client-side variables
clientSide: ['NEXT_PUBLIC_BACKEND_URL', 'NEXT_PUBLIC_API_URL']
});
165 changes: 165 additions & 0 deletions web/src/lib/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// src/lib/api.js - improved version with caching
import { useState, useEffect, useRef } from "react";
import { env } from 'next-runtime-env';

// Cache store
const apiCache = new Map();
const pendingRequests = new Map();

// Cache expiration time (in milliseconds)
const CACHE_EXPIRY = 5000; // 5 seconds

/**
* Client-side fetching with caching
*/
export function useAPIData(endpoint, initialData, refreshInterval = 10000) {
const [data, setData] = useState(initialData);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(!initialData);

// To track if the component is still mounted
const isMounted = useRef(true);

useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false; };
}, []);

useEffect(() => {
const apiUrl = endpoint.startsWith('/api/') ? endpoint : `/api/${endpoint}`;
let intervalId;

const fetchData = async () => {
if (!isMounted.current) return;

try {
setIsLoading(true);
const result = await fetchWithCache(apiUrl);

if (isMounted.current) {
setData(result);
setIsLoading(false);
}
} catch (err) {
if (isMounted.current) {
console.error(`Error fetching ${apiUrl}:`, err);
setError(err.message);
setIsLoading(false);
}
}
};

// Initial fetch
fetchData();

// Set up polling with a more reasonable interval
if (refreshInterval) {
intervalId = setInterval(fetchData, refreshInterval);
}

return () => {
if (intervalId) clearInterval(intervalId);
};
}, [endpoint, refreshInterval]);

return { data, error, isLoading };
}

/**
* Fetch with caching and request deduplication
*/
export async function fetchWithCache(endpoint, options = {}) {
const apiUrl = endpoint.startsWith('/api/') ? endpoint : `/api/${endpoint}`;
const cacheKey = `${apiUrl}-${JSON.stringify(options)}`;

// Check if we have a cached response that's still valid
const cachedData = apiCache.get(cacheKey);
if (cachedData && cachedData.timestamp > Date.now() - CACHE_EXPIRY) {
return cachedData.data;
}

// Check if we already have a pending request for this URL
if (pendingRequests.has(cacheKey)) {
return pendingRequests.get(cacheKey);
}

// Create a new request and store it
const fetchPromise = fetchAPI(apiUrl, options)
.then(data => {
// Store in cache
apiCache.set(cacheKey, {
data,
timestamp: Date.now()
});
// Remove from pending requests
pendingRequests.delete(cacheKey);
return data;
})
.catch(error => {
// Remove from pending requests on error
pendingRequests.delete(cacheKey);
throw error;
});

// Store the pending request
pendingRequests.set(cacheKey, fetchPromise);

return fetchPromise;
}

/**
* Simple fetch with API key
*/
export async function fetchAPI(endpoint, customOptions = {}) {
const apiUrl = endpoint.startsWith('/api/') ? endpoint : `/api/${endpoint}`;

const defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
cache: 'no-store'
};

const options = {
...defaultOptions,
...customOptions,
headers: {
...defaultOptions.headers,
...(customOptions.headers || {})
}
};

const response = await fetch(apiUrl, options);

if (!response.ok) {
const errorText = await response.text();
console.error(`API request failed: ${response.status} - ${errorText} for ${apiUrl}`);
throw new Error(`API request failed: ${response.status} - ${errorText}`);
}

return response.json();
}

/**
* Server-side fetching for Next.js server components
*/
export async function fetchFromAPI(endpoint) {
const apiKey = env('API_KEY');
const baseUrl = env('NEXT_PUBLIC_BACKEND_URL') || 'http://localhost:8090';
const apiUrl = endpoint.startsWith('/api/') ? endpoint : `/api/${endpoint}`;
const url = new URL(apiUrl, baseUrl).toString();

const response = await fetch(url, {
headers: {
'X-API-Key': apiKey
},
cache: 'no-store'
});

if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}

return response.json();
}

0 comments on commit 94b83ff

Please sign in to comment.