Complete Next.js migration with SSR and Docker deployment
Some checks failed
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m26s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped

- Migrate from vanilla JavaScript SPA to Next.js 16 with App Router
- Add server-side rendering for all pages (Home, Compare, Rankings)
- Create individual school pages with dynamic routing (/school/[urn])
- Implement Chart.js and Leaflet map integrations
- Add comprehensive SEO with sitemap, robots.txt, and JSON-LD
- Set up Docker multi-service architecture (PostgreSQL, FastAPI, Next.js)
- Update CI/CD pipeline to build both backend and frontend images
- Fix Dockerfile to include devDependencies for TypeScript compilation
- Add Jest testing configuration
- Implement performance optimizations (code splitting, caching)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Tudor
2026-02-02 20:34:35 +00:00
parent f4919db3b9
commit ff7f5487e6
72 changed files with 18636 additions and 20 deletions

View File

@@ -0,0 +1,59 @@
/**
* LoadingSkeleton Component
* Placeholder for loading states
*/
import styles from './LoadingSkeleton.module.css';
interface LoadingSkeletonProps {
count?: number;
type?: 'card' | 'list' | 'text';
}
export function LoadingSkeleton({ count = 3, type = 'card' }: LoadingSkeletonProps) {
if (type === 'card') {
return (
<div className={styles.grid}>
{Array.from({ length: count }).map((_, i) => (
<div key={i} className={styles.skeletonCard}>
<div className={`${styles.skeleton} ${styles.title}`} />
<div className={styles.meta}>
<div className={`${styles.skeleton} ${styles.tag}`} />
<div className={`${styles.skeleton} ${styles.tag}`} />
</div>
<div className={styles.metrics}>
<div className={`${styles.skeleton} ${styles.metric}`} />
<div className={`${styles.skeleton} ${styles.metric}`} />
<div className={`${styles.skeleton} ${styles.metric}`} />
</div>
<div className={styles.actions}>
<div className={`${styles.skeleton} ${styles.button}`} />
<div className={`${styles.skeleton} ${styles.button}`} />
</div>
</div>
))}
</div>
);
}
if (type === 'list') {
return (
<div className={styles.list}>
{Array.from({ length: count }).map((_, i) => (
<div key={i} className={styles.skeletonListItem}>
<div className={`${styles.skeleton} ${styles.listTitle}`} />
<div className={`${styles.skeleton} ${styles.listText}`} />
</div>
))}
</div>
);
}
return (
<div className={styles.textContainer}>
{Array.from({ length: count }).map((_, i) => (
<div key={i} className={`${styles.skeleton} ${styles.text}`} />
))}
</div>
);
}