Compare commits

...
3 Commits
Author SHA1 Message Date
damjan_savicandClaude Opus 4.5 05e7808d71 Complete Tailwind CSS v4 migration with @theme
- Remove tailwind.config.js (no longer needed in v4)
- Migrate all theme config to CSS @theme directive
- Define colors, fonts, animations in CSS
- Add zinc color palette for direct use
- Fix styling to match original design

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:35:27 +01:00
damjan_savicandClaude Opus 4.5 1f8051ae1a Fix Tailwind CSS v4 migration
- Update globals.css to use @import "tailwindcss" and @config
- Remove incompatible plugins (tailwindcss-animate, @tailwindcss/forms)
- Add @tailwindcss/postcss for v4 compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:33:14 +01:00
damjan_savicandClaude Opus 4.5 90df7bde9e Upgrade to Next.js 16, enable SSR, update packages
- Upgrade all packages to latest versions
- Enable SSR (dynamic = 'force-dynamic') for all pages
- Update PostCSS config for Tailwind CSS v4 (@tailwindcss/postcss)
- Fix Edge-compatible CSRF token generation (Web Crypto API)
- Remove deprecated eslint config from next.config.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:29:46 +01:00
10 changed files with 2488 additions and 2456 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" /> import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+1 -4
View File
@@ -10,10 +10,7 @@ const nextConfig: NextConfig = {
// Enable React strict mode for better error detection // Enable React strict mode for better error detection
reactStrictMode: true, reactStrictMode: true,
// Temporarily ignore ESLint and TypeScript during build (pre-existing issues) // Temporarily ignore TypeScript during build (pre-existing issues)
eslint: {
ignoreDuringBuilds: true,
},
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },
+32 -34
View File
@@ -19,51 +19,49 @@
"test:coverage": "vitest run --coverage" "test:coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"@mdx-js/loader": "^3.1.0", "@mdx-js/loader": "^3.1.1",
"@mdx-js/mdx": "^3.1.0", "@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.0", "@mdx-js/react": "^3.1.1",
"@next-safe/middleware": "^0.10.0", "@next-safe/middleware": "^0.10.0",
"@next/mdx": "^15.1.0", "@next/mdx": "^16.1.6",
"@supabase/ssr": "^0.8.0", "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.39.7", "@supabase/supabase-js": "^2.93.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.18.2", "framer-motion": "^12.29.2",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"lucide-react": "^0.475.0", "lucide-react": "^0.563.0",
"next": "^15.1.0", "next": "^16.1.6",
"next-intl": "^3.26.0", "next-intl": "^4.8.1",
"next-mdx-remote": "^5.0.0", "next-mdx-remote": "^5.0.0",
"openai": "^4.77.0", "openai": "^6.17.0",
"pg": "^8.13.1", "pg": "^8.18.0",
"react": "^19.0.0", "react": "^19.2.4",
"react-dom": "^19.0.0", "react-dom": "^19.2.4",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-intersection-observer": "^9.8.1", "react-intersection-observer": "^10.0.2",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"web-vitals": "^5.1.0" "web-vitals": "^5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.58.0", "@playwright/test": "^1.58.1",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/postcss": "^4.1.18",
"@testing-library/jest-dom": "^6.0.0", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^16.3.2",
"@types/mdx": "^2.0.13", "@types/mdx": "^2.0.13",
"@types/node": "^22.0.0", "@types/node": "^25.1.0",
"@types/pg": "^8.11.10", "@types/pg": "^8.16.0",
"@types/react": "^19.0.0", "@types/react": "^19.2.10",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.2.3",
"@vitest/coverage-v8": "1.6.1", "@vitest/coverage-v8": "4.0.18",
"autoprefixer": "^10.4.18", "autoprefixer": "^10.4.24",
"eslint": "^9.9.1", "eslint": "^9.39.2",
"eslint-config-next": "^15.1.0", "eslint-config-next": "^16.1.6",
"jsdom": "^23.2.0", "jsdom": "^27.4.0",
"playwright-lighthouse": "^4.0.0", "playwright-lighthouse": "^4.0.0",
"postcss": "^8.4.35", "postcss": "^8.5.6",
"sharp": "^0.34.3", "sharp": "^0.34.5",
"tailwindcss": "^3.4.1", "typescript": "^5.9.3",
"typescript": "^5.5.3", "vitest": "^4.0.18"
"vitest": "^1.3.1"
} }
} }
+2341 -2296
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, '@tailwindcss/postcss': {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };
+4
View File
@@ -10,6 +10,10 @@ import { WebVitals } from '@/components/analytics';
import { ChatbotLoader } from '@/components/chat/ChatbotLoader'; import { ChatbotLoader } from '@/components/chat/ChatbotLoader';
import '../globals.css'; import '../globals.css';
// Enable SSR - render pages on each request
export const dynamic = 'force-dynamic';
export const revalidate = 0;
const inter = Inter({ const inter = Inter({
subsets: ['latin'], subsets: ['latin'],
display: 'swap', display: 'swap',
+89 -33
View File
@@ -1,34 +1,90 @@
@tailwind base; @import "tailwindcss";
@tailwind components;
@tailwind utilities;
/* Tailwind CSS v4 Theme Configuration */
@theme {
/* Font Family */
--font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Colors - Zinc-based dark theme */
--color-background: #18181b;
--color-foreground: #fafafa;
--color-card: #27272a;
--color-card-foreground: #fafafa;
--color-primary: #3b82f6;
--color-primary-foreground: #ffffff;
--color-secondary: #3f3f46;
--color-secondary-foreground: #fafafa;
--color-muted: #3f3f46;
--color-muted-foreground: #a1a1aa;
--color-accent: #3f3f46;
--color-accent-foreground: #fafafa;
--color-destructive: #ef4444;
--color-destructive-foreground: #fafafa;
--color-border: #3f3f46;
--color-input: #3f3f46;
--color-ring: #3b82f6;
/* Zinc palette for direct use */
--color-zinc-50: #fafafa;
--color-zinc-100: #f4f4f5;
--color-zinc-200: #e4e4e7;
--color-zinc-300: #d4d4d8;
--color-zinc-400: #a1a1aa;
--color-zinc-500: #71717a;
--color-zinc-600: #52525b;
--color-zinc-700: #3f3f46;
--color-zinc-800: #27272a;
--color-zinc-900: #18181b;
--color-zinc-950: #09090b;
/* Border Radius */
--radius-lg: 0.5rem;
--radius-md: calc(0.5rem - 2px);
--radius-sm: calc(0.5rem - 4px);
/* Animations */
--animate-slide-up: slideUp 0.5s ease-out forwards;
--animate-fade-in: fadeIn 0.3s ease-out forwards;
--animate-spin: spin 1s linear infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--animate-bounce: bounce 1s infinite;
}
/* Keyframes for animations */
@keyframes slideUp {
0% { transform: translateY(10px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes bounce {
0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); }
50% { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
}
/* CSS Variables for legacy support */
:root { :root {
--font-inter: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-inter: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Animation Design Tokens */ /* Animation Design Tokens */
--transition-fast: 150ms; --transition-fast: 150ms;
--transition-base: 200ms; --transition-base: 200ms;
--transition-slow: 300ms; --transition-slow: 300ms;
--transition-slower: 500ms; --transition-slower: 500ms;
/* Colors */
--background: 24 24 27;
--foreground: 250 250 250;
--card: 39 39 42;
--card-foreground: 250 250 250;
--primary: 59 130 246;
--primary-foreground: 255 255 255;
--secondary: 63 63 70;
--secondary-foreground: 250 250 250;
--muted: 63 63 70;
--muted-foreground: 161 161 170;
--accent: 63 63 70;
--accent-foreground: 250 250 250;
--destructive: 239 68 68;
--destructive-foreground: 250 250 250;
--border: 63 63 70;
--input: 63 63 70;
--ring: 59 130 246;
} }
/* Prefers-Reduced-Motion Support */ /* Prefers-Reduced-Motion Support */
@@ -53,8 +109,8 @@ html {
body { body {
font-family: var(--font-inter); font-family: var(--font-inter);
background-color: rgb(var(--background)); background-color: var(--color-background);
color: rgb(var(--foreground)); color: var(--color-foreground);
min-height: 100vh; min-height: 100vh;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@@ -66,26 +122,26 @@ body {
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: rgb(var(--background)); background: var(--color-background);
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: rgb(var(--secondary)); background: var(--color-secondary);
border-radius: 4px; border-radius: 4px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: rgb(var(--muted-foreground)); background: var(--color-muted-foreground);
} }
/* Selection */ /* Selection */
::selection { ::selection {
background-color: rgb(var(--primary) / 0.3); background-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
} }
/* Focus styles */ /* Focus styles */
:focus-visible { :focus-visible {
outline: 2px solid rgb(var(--ring)); outline: 2px solid var(--color-ring);
outline-offset: 2px; outline-offset: 2px;
} }
@@ -97,16 +153,16 @@ button {
/* Utility classes */ /* Utility classes */
.text-gradient { .text-gradient {
background: linear-gradient(135deg, rgb(var(--primary)) 0%, #8b5cf6 100%); background: linear-gradient(135deg, var(--color-primary) 0%, #8b5cf6 100%);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
} }
.glass { .glass {
background: rgb(var(--card) / 0.8); background: color-mix(in srgb, var(--color-card) 80%, transparent);
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
border: 1px solid rgb(var(--border) / 0.5); border: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
} }
/* Safe Area Insets for notches */ /* Safe Area Insets for notches */
+8 -2
View File
@@ -2,11 +2,17 @@ import { chain, chainMatch, isPageRequest, csp } from '@next-safe/middleware';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import createMiddleware from 'next-intl/middleware'; import createMiddleware from 'next-intl/middleware';
import { locales, defaultLocale } from './i18n/config'; import { locales, defaultLocale } from './i18n/config';
import { randomBytes } from 'crypto';
const CSRF_TOKEN_COOKIE_NAME = 'csrf_token'; const CSRF_TOKEN_COOKIE_NAME = 'csrf_token';
const CSRF_TOKEN_LENGTH = 32; const CSRF_TOKEN_LENGTH = 32;
// Edge-compatible random bytes generation
function generateToken(length: number): string {
const bytes = new Uint8Array(length);
crypto.getRandomValues(bytes);
return btoa(String.fromCharCode(...bytes)).replace(/[+/=]/g, '');
}
const handleI18nRouting = createMiddleware({ const handleI18nRouting = createMiddleware({
locales, locales,
defaultLocale, defaultLocale,
@@ -33,7 +39,7 @@ function csrfMiddleware(request: NextRequest, response: NextResponse): NextRespo
const existingToken = request.cookies.get(CSRF_TOKEN_COOKIE_NAME); const existingToken = request.cookies.get(CSRF_TOKEN_COOKIE_NAME);
if (!existingToken) { if (!existingToken) {
const newToken = randomBytes(CSRF_TOKEN_LENGTH).toString('base64url'); const newToken = generateToken(CSRF_TOKEN_LENGTH);
response.cookies.set(CSRF_TOKEN_COOKIE_NAME, newToken, { response.cookies.set(CSRF_TOKEN_COOKIE_NAME, newToken, {
httpOnly: true, httpOnly: true,
-81
View File
@@ -1,81 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
animation: {
'slide-up': 'slideUp 0.5s ease-out forwards',
'fade-in': 'fadeIn 0.3s ease-out forwards',
},
keyframes: {
slideUp: {
'0%': { transform: 'translateY(10px)' },
'100%': { transform: 'translateY(0)' },
},
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
},
colors: {
border: "rgb(var(--border))",
input: "rgb(var(--input))",
ring: "rgb(var(--ring))",
background: "rgb(var(--background))",
foreground: "rgb(var(--foreground))",
primary: {
DEFAULT: "rgb(var(--color-primary))",
dark: "rgb(var(--color-primary-dark))",
},
secondary: {
DEFAULT: "rgb(var(--color-secondary))",
gray: "rgb(var(--color-secondary-gray))",
fuzzy: "rgb(var(--color-secondary-fuzzy))",
},
text: {
DEFAULT: "rgb(var(--color-text))",
light: "rgb(var(--color-text-light))",
},
destructive: {
DEFAULT: "rgb(var(--destructive))",
foreground: "rgb(var(--destructive-foreground))",
},
muted: {
DEFAULT: "rgb(var(--muted))",
foreground: "rgb(var(--muted-foreground))",
},
accent: {
DEFAULT: "rgb(var(--accent))",
foreground: "rgb(var(--accent-foreground))",
},
popover: {
DEFAULT: "rgb(var(--popover))",
foreground: "rgb(var(--popover-foreground))",
},
card: {
DEFAULT: "rgb(var(--card))",
foreground: "rgb(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [
require("tailwindcss-animate"),
require("@tailwindcss/forms")({
strategy: "class",
}),
],
}
+11 -4
View File
@@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@@ -11,7 +15,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
@@ -19,14 +23,17 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
]
} }
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts" ".next/types/**/*.ts",
".next/dev/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",