Convert to Next.js with security features

- Migrate from Vite to Next.js 15 for server-side API support
- Add dynamic API route at /api/filaments that fetches from Confluence
- Implement security measures:
  - API credentials only accessible server-side
  - Security scan script to detect credential leaks
  - Tests to ensure no sensitive data exposure
  - Build-time security checks in CI/CD
- Update AWS Amplify configuration for Next.js deployment
- Update Terraform to use WEB_COMPUTE platform for Next.js
- Add Jest tests for API security
- Remove static JSON approach in favor of dynamic API

This provides real-time data updates while keeping credentials secure on the server.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DaX
2025-06-19 00:11:19 +02:00
parent 4b39190251
commit 21f6577592
22 changed files with 5306 additions and 578 deletions

View File

@@ -1,96 +0,0 @@
import { useState, useEffect } from 'react';
import { FilamentTable } from './components/FilamentTable';
import { Filament } from './types/filament';
import axios from 'axios';
function App() {
const [filaments, setFilaments] = useState<Filament[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
const [darkMode, setDarkMode] = useState(() => {
const saved = localStorage.getItem('darkMode');
return saved ? JSON.parse(saved) : false;
});
useEffect(() => {
localStorage.setItem('darkMode', JSON.stringify(darkMode));
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [darkMode]);
const fetchFilaments = async () => {
try {
setLoading(true);
setError(null);
// In development, use the API endpoint; in production, use the static JSON
const url = import.meta.env.DEV ? '/api/filaments' : '/filaments.json';
const response = await axios.get(url);
setFilaments(response.data);
setLastUpdate(new Date());
} catch (err) {
setError(err instanceof Error ? err.message : 'Greška pri učitavanju filamenata');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchFilaments();
// Refresh every 5 minutes
const interval = setInterval(fetchFilaments, 5 * 60 * 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors">
<header className="bg-white dark:bg-gray-800 shadow transition-colors">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex justify-between items-center">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
Filamenteka
</h1>
<div className="flex items-center gap-4">
{lastUpdate && (
<span className="text-sm text-gray-500 dark:text-gray-400">
Poslednje ažuriranje: {lastUpdate.toLocaleTimeString('sr-RS')}
</span>
)}
<button
onClick={fetchFilaments}
disabled={loading}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Ažuriranje...' : 'Osveži'}
</button>
<button
onClick={() => setDarkMode(!darkMode)}
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
title={darkMode ? 'Svetla tema' : 'Tamna tema'}
>
{darkMode ? '☀️' : '🌙'}
</button>
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<FilamentTable
filaments={filaments}
loading={loading}
error={error || undefined}
/>
</main>
</div>
);
}
export default App;

View File

@@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './styles/index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)