## 🎨 UI/UX Fixes ### Fixed React Hydration Errors - ActivityFeed: Standardized button styling (gradient → solid) - ActivityFeed: Unified icon sizes and spacing for SSR/CSR consistency - ActivityFeed: Added timestamps to chat messages for stable React keys - About: Fixed duplicate keys in tech stack items (added unique key combinations) - Projects: Fixed duplicate keys in project tags (combined projectId + tag + index) ### Fixed Layout Issues - Added spacer after Header component (h-24 md:h-32) to prevent navbar overlap - Hero section now properly visible below fixed navbar ## 🔧 Backend Improvements ### Database Schema - Added ActivityStatus model for real-time activity tracking - Supports: coding activity, music playing, watching, gaming, status/mood - Single-row design (id=1) with auto-updating timestamps ### API Enhancements - Fixed n8n status endpoint to handle missing table gracefully - Added TypeScript interfaces (removed ESLint `any` warnings) - New API: POST /api/n8n/generate-image for AI image generation - New API: GET /api/n8n/generate-image?projectId=X for status check ## 🔐 Security & Auth ### Middleware Updates - Removed premature auth redirect for /manage and /editor routes - Pages now handle their own authentication (show login forms) - Security headers still applied to all routes ## 🤖 New Feature: AI Image Generation System ### Complete automated project cover image generation using local Stable Diffusion **Core Components:** - Admin UI component (AIImageGenerator.tsx) with preview, generate, and regenerate - n8n workflow integration for automation - Context-aware prompt generation based on project metadata - Support for 10+ project categories with optimized prompts **Documentation (6 new files):** - README.md - System overview and features - SETUP.md - Detailed installation guide (486 lines) - QUICKSTART.md - 15-minute quick start - PROMPT_TEMPLATES.md - Category-specific templates (612 lines) - ENVIRONMENT.md - Environment variables reference - n8n-workflow-ai-image-generator.json - Ready-to-import workflow **Database Migration:** - SQL script: create_activity_status.sql - Auto-setup script: quick-fix.sh - Migration guide: prisma/migrations/README.md **Key Features:** ✅ Automatic generation on project creation ✅ Manual regeneration via admin UI ✅ Category-specific prompts (web, mobile, devops, ai, game, etc.) ✅ Local Stable Diffusion (no API costs, privacy-first) ✅ n8n workflow orchestration ✅ Optimized for web (1024x768) ## 📝 Documentation - CHANGELOG_DEV.md - Complete changelog with migration guide - PRE_PUSH_CHECKLIST.md - Pre-push verification checklist - Comprehensive AI image generation docs ## 🐛 Bug Fixes 1. Fixed "Hydration failed" errors in ActivityFeed 2. Fixed "two children with same key" warnings 3. Fixed navbar overlapping hero section 4. Fixed "relation activity_status does not exist" errors 5. Fixed /manage redirect loop (was going to home page) 6. Fixed TypeScript ESLint errors and warnings 7. Fixed duplicate transition prop in Hero component ## ⚠️ Breaking Changes None - All changes are backward compatible ## 🔄 Migration Required Database migration needed for new ActivityStatus table: ```bash ./prisma/migrations/quick-fix.sh # OR psql -d portfolio -f prisma/migrations/create_activity_status.sql ``` ## 📦 Files Changed **Modified (7):** - app/page.tsx - app/components/About.tsx - app/components/Projects.tsx - app/components/ActivityFeed.tsx - app/components/Hero.tsx - app/api/n8n/status/route.ts - middleware.ts - prisma/schema.prisma **Created (14):** - app/api/n8n/generate-image/route.ts - app/components/admin/AIImageGenerator.tsx - docs/ai-image-generation/* (6 files) - prisma/migrations/* (3 files) - CHANGELOG_DEV.md - PRE_PUSH_CHECKLIST.md - COMMIT_MESSAGE.txt ## ✅ Testing - [x] Build successful: npm run build - [x] Linting passed: npm run lint (0 errors, 8 warnings) - [x] No hydration errors in console - [x] No duplicate key warnings - [x] /manage accessible (shows login form) - [x] API endpoints responding correctly - [x] Navbar no longer overlaps content ## 🚀 Next Steps 1. Test AI image generation with Stable Diffusion setup 2. Test n8n workflow integration 3. Create demo screenshots for new features 4. Update main README.md after merge --- Co-authored-by: AI Assistant (Claude Sonnet 4.5)
141 lines
3.8 KiB
TypeScript
141 lines
3.8 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { PrismaClient } from "@prisma/client";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export const dynamic = "force-dynamic";
|
|
export const revalidate = 0;
|
|
|
|
interface ActivityStatusRow {
|
|
id: number;
|
|
activity_type?: string;
|
|
activity_details?: string;
|
|
activity_project?: string;
|
|
activity_language?: string;
|
|
activity_repo?: string;
|
|
music_playing?: boolean;
|
|
music_track?: string;
|
|
music_artist?: string;
|
|
music_album?: string;
|
|
music_platform?: string;
|
|
music_progress?: number;
|
|
music_album_art?: string;
|
|
watching_title?: string;
|
|
watching_platform?: string;
|
|
watching_type?: string;
|
|
gaming_game?: string;
|
|
gaming_platform?: string;
|
|
gaming_status?: string;
|
|
status_mood?: string;
|
|
status_message?: string;
|
|
updated_at: Date;
|
|
}
|
|
|
|
export async function GET() {
|
|
try {
|
|
// Fetch from activity_status table
|
|
const result = await prisma.$queryRawUnsafe<ActivityStatusRow[]>(
|
|
`SELECT * FROM activity_status WHERE id = 1 LIMIT 1`,
|
|
);
|
|
|
|
if (!result || result.length === 0) {
|
|
return NextResponse.json({
|
|
activity: null,
|
|
music: null,
|
|
watching: null,
|
|
gaming: null,
|
|
status: null,
|
|
});
|
|
}
|
|
|
|
const data = result[0];
|
|
|
|
// Check if activity is recent (within last 2 hours)
|
|
const lastUpdate = new Date(data.updated_at);
|
|
const now = new Date();
|
|
const hoursSinceUpdate =
|
|
(now.getTime() - lastUpdate.getTime()) / (1000 * 60 * 60);
|
|
const isRecent = hoursSinceUpdate < 2;
|
|
|
|
return NextResponse.json(
|
|
{
|
|
activity:
|
|
data.activity_type && isRecent
|
|
? {
|
|
type: data.activity_type,
|
|
details: data.activity_details,
|
|
project: data.activity_project,
|
|
language: data.activity_language,
|
|
repo: data.activity_repo,
|
|
link: data.activity_repo, // Use repo URL as link
|
|
timestamp: data.updated_at,
|
|
}
|
|
: null,
|
|
|
|
music: data.music_playing
|
|
? {
|
|
isPlaying: data.music_playing,
|
|
track: data.music_track,
|
|
artist: data.music_artist,
|
|
album: data.music_album,
|
|
platform: data.music_platform || "spotify",
|
|
progress: data.music_progress,
|
|
albumArt: data.music_album_art,
|
|
spotifyUrl: data.music_track
|
|
? `https://open.spotify.com/search/${encodeURIComponent(data.music_track + " " + data.music_artist)}`
|
|
: null,
|
|
}
|
|
: null,
|
|
|
|
watching: data.watching_title
|
|
? {
|
|
title: data.watching_title,
|
|
platform: data.watching_platform || "youtube",
|
|
type: data.watching_type || "video",
|
|
}
|
|
: null,
|
|
|
|
gaming: data.gaming_game
|
|
? {
|
|
game: data.gaming_game,
|
|
platform: data.gaming_platform || "steam",
|
|
status: data.gaming_status || "playing",
|
|
}
|
|
: null,
|
|
|
|
status: data.status_mood
|
|
? {
|
|
mood: data.status_mood,
|
|
customMessage: data.status_message,
|
|
}
|
|
: null,
|
|
},
|
|
{
|
|
headers: {
|
|
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
Pragma: "no-cache",
|
|
},
|
|
},
|
|
);
|
|
} catch (error) {
|
|
console.error("Error fetching activity status:", error);
|
|
|
|
// Return empty state on error (graceful degradation)
|
|
return NextResponse.json(
|
|
{
|
|
activity: null,
|
|
music: null,
|
|
watching: null,
|
|
gaming: null,
|
|
status: null,
|
|
},
|
|
{
|
|
status: 200, // Return 200 to prevent frontend errors
|
|
headers: {
|
|
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
},
|
|
},
|
|
);
|
|
}
|
|
}
|