diff --git a/app/api/generate-portfolio/route.ts b/app/api/generate-portfolio/route.ts index 5e83104..3667527 100644 --- a/app/api/generate-portfolio/route.ts +++ b/app/api/generate-portfolio/route.ts @@ -1,14 +1,21 @@ import { NextRequest, NextResponse } from "next/server"; import { generateJobId, generationStatus } from "./storage"; import { createAdminClient, DATABASE_ID, JOBS_COLLECTION_ID } from "@/lib/appwrite"; +import { PORTFOLIO_TEMPLATES } from "@/lib/templates"; // Fallback function using OpenAI-compatible API (Groq is free and fast) -async function generateWithGroq(userInfo: string) { +async function generateWithGroq(userInfo: string, templateId?: string) { const apiKey = process.env.GROQ_API_KEY; if (!apiKey) { throw new Error("GROQ_API_KEY not configured"); } + const template = templateId && PORTFOLIO_TEMPLATES[templateId as keyof typeof PORTFOLIO_TEMPLATES] + ? PORTFOLIO_TEMPLATES[templateId as keyof typeof PORTFOLIO_TEMPLATES] + : null; + + const templatePrompt = template ? `\n\nSpecific Style Requirements (${template.name}):\n${template.systemPrompt}` : ""; + const response = await fetch("https://api.groq.com/openai/v1/chat/completions", { method: "POST", headers: { @@ -41,7 +48,7 @@ Requirements: 9. Ensure the design is visually appealing and professional 10. Extract and organize all relevant information from the user's data -Return ONLY the complete HTML code, no explanations or markdown code blocks. The HTML should be ready to render directly.` +Return ONLY the complete HTML code, no explanations or markdown code blocks. The HTML should be ready to render directly.${templatePrompt}` } ], temperature: 0.7, @@ -65,6 +72,7 @@ export async function POST(request: NextRequest) { const details = formData.get("details") as string; const cvFile = formData.get("cv") as File | null; const selectedModel = formData.get("model") as string || "gemini-2.5-flash"; + const template = formData.get("template") as string || ""; let userInfo = details || ""; @@ -117,7 +125,7 @@ export async function POST(request: NextRequest) { } // Start async generation (don't await) - generatePortfolioAsync(jobId, userInfo, selectedModel, useDatabase); + generatePortfolioAsync(jobId, userInfo, selectedModel, useDatabase, template); // Return job ID immediately return NextResponse.json({ @@ -135,8 +143,9 @@ export async function POST(request: NextRequest) { } // Async generation function -async function generatePortfolioAsync(jobId: string, userInfo: string, selectedModel: string, useDatabase: boolean = true) { +async function generatePortfolioAsync(jobId: string, userInfo: string, selectedModel: string, useDatabase: boolean = true, templateId?: string) { console.log(`[${jobId}] Starting portfolio generation with Groq using ${useDatabase ? 'database' : 'in-memory'} storage...`); + if (templateId) console.log(`[${jobId}] Requested template: ${templateId}`); try { let portfolio = ""; @@ -145,8 +154,8 @@ async function generatePortfolioAsync(jobId: string, userInfo: string, selectedM // Use Groq for generation try { console.log(`[${jobId}] Using Groq Llama 3.3 70B...`); - portfolio = await generateWithGroq(userInfo); - usedProvider = "Groq Llama 3.3 70B"; + portfolio = await generateWithGroq(userInfo, templateId); + usedProvider = `Groq Llama 3.3 70B (${templateId || 'Default'})`; console.log(`[${jobId}] Portfolio generated successfully with Groq, length:`, portfolio.length); } catch (groqError) { console.error(`[${jobId}] Groq failed:`, (groqError as Error).message); diff --git a/app/api/generate-portfolio/status/route.ts b/app/api/generate-portfolio/status/route.ts index 742b8e8..3d38d57 100644 --- a/app/api/generate-portfolio/status/route.ts +++ b/app/api/generate-portfolio/status/route.ts @@ -55,7 +55,10 @@ export async function GET(request: NextRequest) { } console.log(`[Status Check] Job ${jobId} not found in database or memory`); - console.log(`[Status Check] Available jobs in memory:`, Array.from(generationStatus.keys())); + console.log(`[Status Check] Current memory job count: ${generationStatus.size}`); + if (generationStatus.size > 0) { + console.log(`[Status Check] Available jobs in memory:`, Array.from(generationStatus.keys()).slice(0, 5)); + } return NextResponse.json({ error: "Job not found" }, { status: 404 }); } } catch (error) { diff --git a/app/api/generate-portfolio/storage.ts b/app/api/generate-portfolio/storage.ts index e983471..760aceb 100644 --- a/app/api/generate-portfolio/storage.ts +++ b/app/api/generate-portfolio/storage.ts @@ -1,11 +1,19 @@ // Shared in-memory storage for generation status -// In production, replace with Redis or a database -export const generationStatus = new Map(); +// Use globalThis to persist during HMR in development +const globalForGeneration = globalThis as unknown as { + generationStatus: Map; +}; + +export const generationStatus = globalForGeneration.generationStatus || new Map(); + +if (process.env.NODE_ENV !== 'production') { + globalForGeneration.generationStatus = generationStatus; +} // Generate unique job ID export function generateJobId(): string { diff --git a/app/auth/check-email/page.tsx b/app/auth/check-email/page.tsx index 100a0c8..fbc8360 100644 --- a/app/auth/check-email/page.tsx +++ b/app/auth/check-email/page.tsx @@ -4,48 +4,60 @@ import { useSearchParams } from "next/navigation"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Mail, AlertCircle, Sparkles } from "lucide-react"; import { Suspense } from "react"; +import { ThemeToggle } from "@/components/theme-toggle"; function CheckEmailContent() { const searchParams = useSearchParams(); const error = searchParams.get("error"); return ( -
- +
+ {/* Theme Toggle */} +
+ +
+ + {/* Subtle Background Decoration */} +
+
+
+
+ +
-
- +
+
- + Check your email - + We've sent you a verification link {error && ( -
- -

{error}

+
+ +

{error}

)}
-

+

We have sent a verification link to your email address.

-

+

Please check your inbox and click the link to verify your account before accessing the dashboard.

-
- -

+

+ +

Don't see the email? Check your spam folder or request a new verification link.

diff --git a/app/auth/forgot-password/page.tsx b/app/auth/forgot-password/page.tsx index 7ac7e6e..2172b03 100644 --- a/app/auth/forgot-password/page.tsx +++ b/app/auth/forgot-password/page.tsx @@ -15,31 +15,37 @@ import { import { Mail, ArrowLeft, Sparkles, AlertCircle } from "lucide-react"; import { forgotPassword } from "@/lib/actions/auth"; +import { ThemeToggle } from "@/components/theme-toggle"; export default function ForgotPasswordPage() { const [email, setEmail] = useState(""); const [state, formAction, isPending] = useActionState(forgotPassword, null); return ( -
+
+ {/* Theme Toggle */} +
+ +
+ {/* Subtle Background Decoration */}
-
-
+
+
- + {/* Logo/Brand */}
- - + +
- + Forgot your password? - + Enter the email associated with your account
@@ -47,18 +53,18 @@ export default function ForgotPasswordPage() { {/* Error Message */} {state?.error && ( -
- -

+

+ +

{state.error}

)} {/* Info Message */} -
- -

+

+ +

We'll send you a link to reset your password

@@ -66,11 +72,11 @@ export default function ForgotPasswordPage() { {/* Email Input Form */}
-
@@ -88,7 +94,7 @@ export default function ForgotPasswordPage() { @@ -97,7 +103,7 @@ export default function ForgotPasswordPage() { {/* Divider */}
- +
@@ -105,7 +111,7 @@ export default function ForgotPasswordPage() {
Back to Sign In @@ -114,10 +120,10 @@ export default function ForgotPasswordPage() { {/* Alternative: Create Account */}
- Don't have an account? + Don't have an account? Sign up diff --git a/app/auth/reset-password/page.tsx b/app/auth/reset-password/page.tsx index 60d5a41..99707f5 100644 --- a/app/auth/reset-password/page.tsx +++ b/app/auth/reset-password/page.tsx @@ -1,6 +1,5 @@ "use client"; -import { useState, useActionState } from "react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -13,10 +12,11 @@ import { CardTitle, } from "@/components/ui/card"; import { Eye, EyeOff, Lock, CheckCircle2, XCircle, Sparkles, AlertCircle } from "lucide-react"; +import { ThemeToggle } from "@/components/theme-toggle"; import { resetPassword } from "@/lib/actions/auth"; import { useSearchParams } from "next/navigation"; -import { Suspense } from "react"; +import { useState, useActionState, Suspense } from "react"; function ResetPasswordForm() { const searchParams = useSearchParams(); @@ -32,13 +32,20 @@ function ResetPasswordForm() { const passwordsMatch = password && confirmPassword && password === confirmPassword; const passwordsDontMatch = password && confirmPassword && password !== confirmPassword; + let confirmPasswordBorder = ""; + if (passwordsMatch) { + confirmPasswordBorder = "border-emerald-500"; + } else if (passwordsDontMatch) { + confirmPasswordBorder = "border-destructive"; + } + return ( {/* Error Message */} {state?.error && ( -
- -

+

+ +

{state.error}

@@ -48,11 +55,11 @@ function ResetPasswordForm() {
-
-