Merge version_4 into main #6

Merged
bender merged 5 commits from version_4 into main 2026-03-04 19:04:27 +00:00
5 changed files with 862 additions and 198 deletions

418
src/app/app/page.tsx Normal file
View File

@@ -0,0 +1,418 @@
"use client";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleFullscreen from '@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen';
import FooterMedia from '@/components/sections/footer/FooterMedia';
import { useState, useEffect } from "react";
import { ChefHat, LogOut, User, Heart, Settings, Plus, X, Sparkles, Clock, Users2 } from "lucide-react";
import Link from "next/link";
export default function AppPage() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState<any>(null);
const [ingredients, setIngredients] = useState<string[]>([]);
const [currentInput, setCurrentInput] = useState("");
const [recipes, setRecipes] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [savedRecipes, setSavedRecipes] = useState<any[]>([]);
const [showSavedOnly, setShowSavedOnly] = useState(false);
const [showProfile, setShowProfile] = useState(false);
useEffect(() => {
// Check if user is logged in
const checkAuth = async () => {
try {
const response = await fetch('/api/auth/me');
if (response.ok) {
const data = await response.json();
setUser(data.user);
setIsLoggedIn(true);
setSavedRecipes(data.user.savedRecipes || []);
} else {
window.location.href = '/auth';
}
} catch (error) {
window.location.href = '/auth';
}
};
checkAuth();
}, []);
const addIngredient = () => {
if (currentInput.trim()) {
setIngredients([...ingredients, currentInput.trim()]);
setCurrentInput("");
}
};
const removeIngredient = (index: number) => {
setIngredients(ingredients.filter((_, i) => i !== index));
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
addIngredient();
}
};
const generateRecipes = async () => {
if (ingredients.length === 0) {
alert("Please add at least one ingredient");
return;
}
setLoading(true);
try {
const response = await fetch("/api/recipes/generate", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ingredients })
});
if (response.ok) {
const data = await response.json();
setRecipes(data.recipes || []);
}
} catch (error) {
console.error("Recipe generation failed:", error);
} finally {
setLoading(false);
}
};
const saveRecipe = async (recipe: any) => {
try {
const response = await fetch("/api/recipes/save", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recipe })
});
if (response.ok) {
setSavedRecipes([...savedRecipes, recipe]);
}
} catch (error) {
console.error("Failed to save recipe:", error);
}
};
const handleLogout = async () => {
try {
await fetch("/api/auth/logout", { method: "POST" });
window.location.href = "/";
} catch (error) {
console.error("Logout failed:", error);
}
};
if (!isLoggedIn || !user) {
return (
<ThemeProvider
defaultButtonVariant="expand-hover"
defaultTextAnimation="entrance-slide"
borderRadius="pill"
contentWidth="mediumLarge"
sizing="mediumLargeSizeLargeTitles"
background="none"
cardStyle="glass-elevated"
primaryButtonStyle="gradient"
secondaryButtonStyle="glass"
headingFontWeight="semibold"
>
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="inline-block animate-spin mb-4">
<Sparkles size={40} className="text-purple-600" />
</div>
<p className="text-gray-600 dark:text-gray-300">Loading your kitchen...</p>
</div>
</div>
</ThemeProvider>
);
}
const displayRecipes = showSavedOnly ? savedRecipes : recipes;
return (
<ThemeProvider
defaultButtonVariant="expand-hover"
defaultTextAnimation="entrance-slide"
borderRadius="pill"
contentWidth="mediumLarge"
sizing="mediumLargeSizeLargeTitles"
background="none"
cardStyle="glass-elevated"
primaryButtonStyle="gradient"
secondaryButtonStyle="glass"
headingFontWeight="semibold"
>
<div id="nav" data-section="nav">
<NavbarStyleFullscreen
brandName="EasyRecipes"
navItems={[
{ name: "Home", id: "/" },
{ name: "Features", id: "/#features" },
{ name: "Auth", id: "/auth" },
{ name: "App", id: "/app" },
{ name: "Contact", id: "/#contact" }
]}
bottomLeftText={`Welcome, ${user?.fullName}`}
bottomRightText={user?.email}
/>
</div>
<div id="app" data-section="app" className="min-h-screen py-20 px-4 bg-gradient-to-br from-purple-50 via-white to-blue-50 dark:from-gray-950 dark:via-gray-900 dark:to-gray-950">
<div className="max-w-6xl mx-auto">
{/* Header with Profile */}
<div className="flex items-center justify-between mb-12">
<div className="flex items-center gap-4">
<div className="w-16 h-16 bg-gradient-to-r from-purple-600 to-blue-600 rounded-full flex items-center justify-center">
<ChefHat size={32} className="text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Welcome back!</h1>
<p className="text-gray-600 dark:text-gray-400">{user?.email}</p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowProfile(!showProfile)}
className="p-3 bg-white dark:bg-gray-800 rounded-full shadow-lg hover:shadow-xl transition-shadow"
>
<User size={20} className="text-gray-600 dark:text-gray-300" />
</button>
<button
onClick={handleLogout}
className="p-3 bg-red-500/10 hover:bg-red-500/20 rounded-full transition-colors"
>
<LogOut size={20} className="text-red-600" />
</button>
</div>
</div>
{/* Profile Card */}
{showProfile && (
<div className="mb-8 backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-3xl p-8 shadow-xl">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">Your Profile</h2>
<button onClick={() => setShowProfile(false)}>
<X size={24} className="text-gray-600 dark:text-gray-300" />
</button>
</div>
<div className="grid md:grid-cols-3 gap-6">
<div className="bg-gradient-to-br from-purple-500/20 to-blue-500/20 rounded-2xl p-6">
<p className="text-gray-600 dark:text-gray-400 text-sm">Full Name</p>
<p className="text-xl font-bold text-gray-900 dark:text-white">{user?.fullName}</p>
</div>
<div className="bg-gradient-to-br from-purple-500/20 to-blue-500/20 rounded-2xl p-6">
<p className="text-gray-600 dark:text-gray-400 text-sm">Email</p>
<p className="text-xl font-bold text-gray-900 dark:text-white break-all">{user?.email}</p>
</div>
<div className="bg-gradient-to-br from-purple-500/20 to-blue-500/20 rounded-2xl p-6">
<p className="text-gray-600 dark:text-gray-400 text-sm">Saved Recipes</p>
<p className="text-xl font-bold text-gray-900 dark:text-white">{savedRecipes.length}</p>
</div>
</div>
<button className="mt-6 w-full py-3 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white rounded-xl font-semibold flex items-center justify-center gap-2 transition-all">
<Settings size={20} />
Account Settings
</button>
</div>
)}
{/* Main Generator Section */}
<div className="grid lg:grid-cols-3 gap-8">
{/* Left - Ingredient Input */}
<div className="lg:col-span-1">
<div className="backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-3xl p-8 shadow-xl sticky top-24">
<h3 className="text-xl font-bold mb-6 text-gray-900 dark:text-white flex items-center gap-2">
<Plus size={24} />
Your Ingredients
</h3>
{/* Input */}
<div className="flex gap-2 mb-6">
<input
type="text"
value={currentInput}
onChange={(e) => setCurrentInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="e.g., chicken"
className="flex-1 px-4 py-2.5 bg-white/10 border border-white/20 rounded-xl focus:outline-none focus:border-purple-500 dark:bg-white/5 dark:border-white/10 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
/>
<button
onClick={addIngredient}
className="px-4 py-2.5 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white rounded-xl font-semibold transition-all"
>
Add
</button>
</div>
{/* Ingredients List */}
{ingredients.length > 0 && (
<div className="mb-8">
<h4 className="font-semibold text-gray-900 dark:text-white mb-3">Added ({ingredients.length}):</h4>
<div className="space-y-2">
{ingredients.map((ingredient, index) => (
<div
key={index}
className="flex items-center justify-between bg-gradient-to-r from-purple-500/20 to-blue-500/20 px-4 py-2 rounded-xl"
>
<span className="text-gray-900 dark:text-white font-medium">{ingredient}</span>
<button
onClick={() => removeIngredient(index)}
className="text-red-500 hover:text-red-600 transition-colors"
>
<X size={18} />
</button>
</div>
))}
</div>
</div>
)}
{/* Generate Button */}
<button
onClick={generateRecipes}
disabled={loading || ingredients.length === 0}
className="w-full py-3 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl font-semibold flex items-center justify-center gap-2 transition-all group"
>
<Sparkles size={20} className="group-hover:rotate-12 transition-transform" />
{loading ? 'Generating...' : 'Generate Recipes'}
</button>
</div>
</div>
{/* Right - Recipes Display */}
<div className="lg:col-span-2">
{/* Toggle Buttons */}
<div className="flex gap-2 mb-6">
<button
onClick={() => setShowSavedOnly(false)}
className={`px-6 py-3 rounded-xl font-semibold transition-all ${
!showSavedOnly
? 'bg-gradient-to-r from-purple-600 to-blue-600 text-white'
: 'bg-white/10 dark:bg-white/5 border border-white/20 text-gray-900 dark:text-white'
}`}
>
Generated ({recipes.length})
</button>
<button
onClick={() => setShowSavedOnly(true)}
className={`px-6 py-3 rounded-xl font-semibold transition-all flex items-center gap-2 ${
showSavedOnly
? 'bg-gradient-to-r from-purple-600 to-blue-600 text-white'
: 'bg-white/10 dark:bg-white/5 border border-white/20 text-gray-900 dark:text-white'
}`}
>
<Heart size={18} />
Saved ({savedRecipes.length})
</button>
</div>
{/* Recipes Grid */}
{displayRecipes.length > 0 ? (
<div className="space-y-4">
{displayRecipes.map((recipe, index) => (
<div
key={index}
className="backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-2xl p-6 shadow-lg hover:shadow-xl transition-all group"
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<h4 className="text-xl font-bold text-gray-900 dark:text-white mb-2 group-hover:text-purple-600 transition-colors">
{recipe.name}
</h4>
<p className="text-gray-600 dark:text-gray-300 text-sm mb-3">
{recipe.description}
</p>
</div>
<button
onClick={() => saveRecipe(recipe)}
className="p-2 hover:bg-red-500/20 rounded-full transition-colors"
>
<Heart size={20} className={savedRecipes.some(r => r.name === recipe.name) ? 'fill-red-500 text-red-500' : 'text-gray-400'} />
</button>
</div>
<div className="grid md:grid-cols-2 gap-4 mb-4">
<div>
<h5 className="font-semibold text-gray-900 dark:text-white text-sm mb-2">Ingredients:</h5>
<ul className="space-y-1">
{recipe.ingredients?.slice(0, 4).map((ing: string, i: number) => (
<li key={i} className="text-sm text-gray-600 dark:text-gray-300 flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-purple-500 rounded-full"></span>
{ing}
</li>
))}
{recipe.ingredients?.length > 4 && (
<li className="text-sm text-gray-500 dark:text-gray-400">+{recipe.ingredients.length - 4} more</li>
)}
</ul>
</div>
<div className="flex items-center justify-between text-sm bg-gradient-to-br from-purple-500/20 to-blue-500/20 rounded-xl p-3">
<div className="flex items-center gap-1 text-gray-600 dark:text-gray-300">
<Clock size={16} />
<span>{recipe.cookTime || 30} mins</span>
</div>
<div className="flex items-center gap-1 text-gray-600 dark:text-gray-300">
<Users2 size={16} />
<span>Serves {recipe.servings || 2}</span>
</div>
</div>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300 line-clamp-2">
{recipe.instructions}
</p>
</div>
))}
</div>
) : (
<div className="backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-3xl p-12 text-center">
<Sparkles size={48} className="mx-auto mb-4 text-purple-400 opacity-50" />
<p className="text-gray-600 dark:text-gray-300">
{showSavedOnly
? "No saved recipes yet. Generate some and save your favorites!"
: "Add ingredients and generate recipes to get started"}
</p>
</div>
)}
</div>
</div>
</div>
</div>
<div id="footer" data-section="footer">
<FooterMedia
imageSrc="https://images.unsplash.com/photo-1495521821757-a1efb6729352?w=1920&q=80"
imageAlt="Footer background"
columns={[
{
title: "App", items: [
{ label: "Recipe Generator", href: "/app" },
{ label: "My Recipes", href: "/app" },
{ label: "Settings", href: "/app" }
]
},
{
title: "Account", items: [
{ label: "Profile", href: "/app" },
{ label: "Preferences", href: "/app" },
{ label: "Security", href: "/app" }
]
},
{
title: "Support", items: [
{ label: "Help Center", href: "/" },
{ label: "Privacy Policy", href: "/" },
{ label: "Terms of Service", href: "/" }
]
}
]}
logoText="EasyRecipes"
copyrightText="© 2025 EasyRecipes. All rights reserved."
/>
</div>
</ThemeProvider>
);
}

298
src/app/auth/page.tsx Normal file
View File

@@ -0,0 +1,298 @@
"use client";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleFullscreen from '@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen';
import { useState } from "react";
import Link from "next/link";
import { Mail, Lock, User, Eye, EyeOff, ArrowRight, CheckCircle, AlertCircle } from "lucide-react";
export default function AuthPage() {
const [isLogin, setIsLogin] = useState(true);
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error' | ''; text: string }>({ type: '', text: '' });
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: '',
fullName: ''
});
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setMessage({ type: '', text: '' });
try {
const endpoint = isLogin ? '/api/auth/login' : '/api/auth/signup';
const body = isLogin
? { email: formData.email, password: formData.password }
: { email: formData.email, password: formData.password, fullName: formData.fullName };
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await response.json();
if (response.ok) {
setMessage({ type: 'success', text: isLogin ? 'Login successful!' : 'Account created successfully!' });
setTimeout(() => {
window.location.href = '/app';
}, 1500);
} else {
setMessage({ type: 'error', text: data.error || 'Something went wrong' });
}
} catch (error) {
setMessage({ type: 'error', text: 'Connection error. Please try again.' });
} finally {
setLoading(false);
}
};
const handleForgotPassword = async () => {
if (!formData.email) {
setMessage({ type: 'error', text: 'Please enter your email address' });
return;
}
setLoading(true);
try {
const response = await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: formData.email })
});
if (response.ok) {
setMessage({ type: 'success', text: 'Recovery email sent! Check your inbox.' });
} else {
setMessage({ type: 'error', text: 'Email not found' });
}
} catch (error) {
setMessage({ type: 'error', text: 'Failed to send recovery email' });
} finally {
setLoading(false);
}
};
return (
<ThemeProvider
defaultButtonVariant="expand-hover"
defaultTextAnimation="entrance-slide"
borderRadius="pill"
contentWidth="mediumLarge"
sizing="mediumLargeSizeLargeTitles"
background="none"
cardStyle="glass-elevated"
primaryButtonStyle="gradient"
secondaryButtonStyle="glass"
headingFontWeight="semibold"
>
<div id="nav" data-section="nav">
<NavbarStyleFullscreen
brandName="EasyRecipes"
navItems={[
{ name: "Home", id: "/" },
{ name: "Features", id: "/#features" },
{ name: "Auth", id: "/auth" },
{ name: "App", id: "/app" },
{ name: "Contact", id: "/#contact" }
]}
bottomLeftText="Secure Login"
bottomRightText="hello@easyrecipes.com"
/>
</div>
<div id="auth" data-section="auth" className="min-h-screen flex items-center justify-center py-12 px-4">
<div className="w-full max-w-md">
{/* Liquid Glass Card */}
<div className="backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-3xl p-8 shadow-2xl">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-blue-600">
{isLogin ? 'Welcome Back' : 'Join EasyRecipes'}
</h1>
<p className="text-gray-600 dark:text-gray-300">
{isLogin ? 'Sign in to your account' : 'Create your account to get started'}
</p>
</div>
{/* Message Display */}
{message.text && (
<div className={`mb-6 p-4 rounded-xl flex items-center gap-3 ${
message.type === 'success'
? 'bg-green-500/20 border border-green-500/50 text-green-700 dark:text-green-300'
: 'bg-red-500/20 border border-red-500/50 text-red-700 dark:text-red-300'
}`}>
{message.type === 'success' ? (
<CheckCircle size={20} />
) : (
<AlertCircle size={20} />
)}
<span>{message.text}</span>
</div>
)}
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{!isLogin && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Full Name
</label>
<div className="relative">
<User className="absolute left-4 top-3.5 text-gray-400" size={20} />
<input
type="text"
name="fullName"
value={formData.fullName}
onChange={handleInputChange}
placeholder="John Doe"
className="w-full pl-12 pr-4 py-3 bg-white/10 border border-white/20 rounded-xl focus:outline-none focus:border-purple-500 dark:bg-white/5 dark:border-white/10 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
required
/>
</div>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email Address
</label>
<div className="relative">
<Mail className="absolute left-4 top-3.5 text-gray-400" size={20} />
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder="you@example.com"
className="w-full pl-12 pr-4 py-3 bg-white/10 border border-white/20 rounded-xl focus:outline-none focus:border-purple-500 dark:bg-white/5 dark:border-white/10 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password
</label>
<div className="relative">
<Lock className="absolute left-4 top-3.5 text-gray-400" size={20} />
<input
type={showPassword ? 'text' : 'password'}
name="password"
value={formData.password}
onChange={handleInputChange}
placeholder="Enter your password"
className="w-full pl-12 pr-12 py-3 bg-white/10 border border-white/20 rounded-xl focus:outline-none focus:border-purple-500 dark:bg-white/5 dark:border-white/10 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-3.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
</div>
</div>
{!isLogin && (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Confirm Password
</label>
<div className="relative">
<Lock className="absolute left-4 top-3.5 text-gray-400" size={20} />
<input
type={showPassword ? 'text' : 'password'}
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleInputChange}
placeholder="Confirm your password"
className="w-full pl-12 pr-12 py-3 bg-white/10 border border-white/20 rounded-xl focus:outline-none focus:border-purple-500 dark:bg-white/5 dark:border-white/10 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
required
/>
</div>
</div>
)}
{/* Forgot Password */}
{isLogin && (
<div className="text-right">
<button
type="button"
onClick={handleForgotPassword}
className="text-sm text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors"
>
Forgot password?
</button>
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={loading}
className="w-full py-3 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 disabled:opacity-50 text-white font-semibold rounded-xl flex items-center justify-center gap-2 transition-all duration-300 group"
>
{loading ? 'Processing...' : isLogin ? 'Sign In' : 'Create Account'}
{!loading && <ArrowRight size={20} className="group-hover:translate-x-1 transition-transform" />}
</button>
</form>
{/* Toggle Auth Mode */}
<div className="mt-6 text-center">
<p className="text-gray-600 dark:text-gray-400">
{isLogin ? "Don't have an account? " : 'Already have an account? '}
<button
onClick={() => setIsLogin(!isLogin)}
className="text-purple-600 dark:text-purple-400 font-semibold hover:text-purple-700 dark:hover:text-purple-300 transition-colors"
>
{isLogin ? 'Sign Up' : 'Sign In'}
</button>
</p>
</div>
{/* Divider */}
<div className="my-6 flex items-center gap-3">
<div className="flex-1 h-px bg-white/20"></div>
<span className="text-sm text-gray-500 dark:text-gray-400">or</span>
<div className="flex-1 h-px bg-white/20"></div>
</div>
{/* OAuth Buttons */}
<div className="space-y-3">
<button
type="button"
className="w-full py-2.5 bg-white/5 dark:bg-white/5 border border-white/20 hover:border-white/40 rounded-xl text-gray-900 dark:text-white font-medium transition-all duration-300 flex items-center justify-center gap-2"
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
</svg>
Continue with Google
</button>
</div>
</div>
{/* Security Note */}
<div className="mt-8 p-4 bg-blue-500/10 border border-blue-500/30 rounded-xl">
<p className="text-sm text-blue-700 dark:text-blue-300 text-center">
🔒 Your data is encrypted and secure. We never share your information.
</p>
</div>
</div>
</div>
</ThemeProvider>
);
}

View File

@@ -1,5 +1,74 @@
@import "tailwindcss";
@import "./styles/variables.css";
@import "./styles/theme.css";
@import "./styles/utilities.css";
@import "./styles/base.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply antialiased;
}
h1, h2, h3, h4, h5, h6 {
@apply font-bold;
}
}
@layer components {
.rounded-theme {
@apply rounded-2xl;
}
.rounded-theme-capped {
@apply rounded-3xl;
}
.glass {
@apply backdrop-blur-md bg-white/10 border border-white/20 rounded-2xl;
}
.glass-elevated {
@apply backdrop-blur-xl bg-white/10 dark:bg-white/5 border border-white/20 dark:border-white/10 rounded-3xl shadow-2xl;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.w-content-width {
width: var(--width-content-width);
}
.max-w-content-width {
max-width: var(--width-content-width);
}
.mask-fade-top-long {
mask-image: linear-gradient(to bottom, transparent 0%, black 20%);
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 20%);
}
.mask-fade-bottom {
mask-image: linear-gradient(to top, transparent 0%, black 20%);
-webkit-mask-image: linear-gradient(to top, transparent 0%, black 20%);
}
.mask-fade-left {
mask-image: linear-gradient(to right, transparent 0%, black 20%);
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 20%);
}
.mask-fade-right {
mask-image: linear-gradient(to left, transparent 0%, black 20%);
-webkit-mask-image: linear-gradient(to left, transparent 0%, black 20%);
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
}

View File

@@ -1,37 +1,25 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./styles/variables.css";
import "./globals.css";
import { ServiceWrapper } from "@/components/ServiceWrapper";
import Tag from "@/tag/Tag";
const inter = Inter({
variable: "--font-inter", subsets: ["latin"],
});
export const metadata: Metadata = {
title: "EasyRecipes - AI-Powered Recipe Generation", description: "Discover amazing recipes with AI recipe generation, ingredient scanner, and smart pantry system. Create delicious meals with what you have.", keywords: "recipe generator, AI recipes, ingredient scanner, smart pantry, cooking app", openGraph: {
title: "EasyRecipes - AI-Powered Recipe Generation", description: "Transform your cooking with AI-powered recipes and smart ingredient management.", siteName: "EasyRecipes", type: "website"
},
robots: {
index: true,
follow: true,
},
};
title: "EasyRecipes - AI-Powered Recipe Generator", description: "Generate delicious recipes from your ingredients using AI"};
export default function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
}) {
return (
<html lang="en" suppressHydrationWarning>
<ServiceWrapper>
<body
className={`${inter.variable} antialiased`}
>
<Tag />
{children}
<html lang="en">
<body className={inter.variable}>
{children}
<script
dangerouslySetInnerHTML={{
__html: `
@@ -1399,7 +1387,6 @@ export default function RootLayout({
}}
/>
</body>
</ServiceWrapper>
</html>
);
}

View File

@@ -1,250 +1,142 @@
"use client";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarLayoutFloatingInline from '@/components/navbar/NavbarLayoutFloatingInline';
import HeroBillboardTestimonial from '@/components/sections/hero/HeroBillboardTestimonial';
import FeatureCardMedia from '@/components/sections/feature/FeatureCardMedia';
import ProductCardThree from '@/components/sections/product/ProductCardThree';
import NavbarStyleFullscreen from '@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen';
import HeroCarouselLogo from '@/components/sections/hero/heroCarouselLogo/HeroCarouselLogo';
import TextAbout from '@/components/sections/about/TextAbout';
import TestimonialCardFive from '@/components/sections/testimonial/TestimonialCardFive';
import PricingCardThree from '@/components/sections/pricing/PricingCardThree';
import ContactText from '@/components/sections/contact/ContactText';
import FooterBaseReveal from '@/components/sections/footer/FooterBaseReveal';
import { Sparkles, CheckCircle, Star, Heart, Zap } from "lucide-react";
import FeatureCardMedia from '@/components/sections/feature/FeatureCardMedia';
import FooterMedia from '@/components/sections/footer/FooterMedia';
import BlurBottomBackground from '@/components/background/BlurBottomBackground';
export default function LandingPage() {
export default function HomePage() {
return (
<ThemeProvider
defaultButtonVariant="hover-magnetic"
defaultTextAnimation="reveal-blur"
defaultButtonVariant="expand-hover"
defaultTextAnimation="entrance-slide"
borderRadius="pill"
contentWidth="mediumLarge"
sizing="mediumLargeSizeLargeTitles"
background="floatingGradient"
background="none"
cardStyle="glass-elevated"
primaryButtonStyle="radial-glow"
primaryButtonStyle="gradient"
secondaryButtonStyle="glass"
headingFontWeight="semibold"
>
<div id="nav" data-section="nav">
<NavbarLayoutFloatingInline
<NavbarStyleFullscreen
brandName="EasyRecipes"
navItems={[
{ name: "Home", id: "/" },
{ name: "Features", id: "/features" },
{ name: "Download", id: "/download" },
{ name: "Generate", id: "/generate" },
{ name: "Contact", id: "/contact" }
{ name: "Features", id: "#features" },
{ name: "Auth", id: "/auth" },
{ name: "App", id: "/app" },
{ name: "Contact", id: "#contact" }
]}
button={{ text: "Get Started", href: "/download" }}
animateOnLoad={true}
bottomLeftText="AI-Powered Recipes"
bottomRightText="hello@easyrecipes.com"
/>
</div>
<div id="hero" data-section="hero">
<HeroBillboardTestimonial
title="Create Amazing Recipes with AI"
description="Discover personalized recipes powered by artificial intelligence. Scan ingredients, manage your pantry, and cook like a chef with EasyRecipes."
tag="AI-Powered Cooking"
tagIcon={Sparkles}
tagAnimation="slide-up"
imageSrc="https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=800&q=80"
imageAlt="AI recipe generation showcase"
mediaAnimation="slide-up"
background={{ variant: "sparkles-gradient" }}
<HeroCarouselLogo
logoText="EASYRECIPES"
description="Transform your kitchen with AI-powered recipes. Discover delicious meals from ingredients you already have."
buttons={[
{ text: "Explore Recipes", href: "/features" },
{ text: "Download App", href: "/download" }
{ text: "Get Started", href: "/auth" },
{ text: "Learn More", href: "#features" }
]}
buttonAnimation="slide-up"
testimonials={[
slides={[
{
name: "Emma Thompson", handle: "Home Chef", testimonial: "EasyRecipes transformed how I cook! The AI suggestions are spot-on for my ingredients.", rating: 5,
imageSrc: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&q=80"
imageSrc: "https://images.unsplash.com/photo-1495521821757-a1efb6729352?w=1920&q=80", imageAlt: "Fresh ingredients"
},
{
name: "Marcus Johnson", handle: "Busy Professional", testimonial: "Love the ingredient scanner! Saves me so much time figuring out what to cook.", rating: 5,
imageSrc: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&q=80"
imageSrc: "https://images.unsplash.com/photo-1495697542231-dc1d53f6f0bb?w=1920&q=80", imageAlt: "Cooking in kitchen"
},
{
name: "Sarah Chen", handle: "Food Enthusiast", testimonial: "The smart pantry system is a game-changer. Never waste ingredients again!", rating: 5,
imageSrc: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&q=80"
imageSrc: "https://images.unsplash.com/photo-1495863359367-055fb6aae5e0?w=1920&q=80", imageAlt: "Finished dish"
}
]}
testimonialRotationInterval={5000}
useInvertedBackground={false}
autoplayDelay={5000}
showDimOverlay={true}
/>
</div>
<div id="features" data-section="features">
<FeatureCardMedia
title="Powerful Features for Smart Cooking"
description="Everything you need to cook smarter, faster, and with less waste"
tag="Core Features"
tagIcon={CheckCircle}
textboxLayout="default"
animationType="slide-up"
useInvertedBackground={true}
features={[
{
id: "1", tag: "AI Generation", title: "AI Recipe Generation", description: "Get personalized recipe suggestions based on your ingredients, dietary preferences, and cooking skill level. Our AI learns what you love to cook.", imageSrc: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=600&q=80&_wi=1", imageAlt: "AI recipe generation"
id: "1", title: "Secure Authentication", description: "Create your account with email and password. Your data is encrypted and protected with industry-standard security.", tag: "Security First", imageSrc: "https://images.unsplash.com/photo-1555949519-ef32f108e557?w=600&q=80", imageAlt: "Secure lock icon"
},
{
id: "2", tag: "Scanning", title: "Ingredient Scanner", description: "Simply take a photo of your ingredients or groceries. Our advanced scanner identifies items instantly and adds them to your pantry.", imageSrc: "https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=600&q=80&_wi=1", imageAlt: "Ingredient scanner technology"
id: "2", title: "Personalized Profile", description: "Save your favorite recipes and manage your ingredient preferences. Your profile grows smarter with every recipe.", tag: "Your Kitchen", imageSrc: "https://images.unsplash.com/photo-1552664730-d307ca884978?w=600&q=80", imageAlt: "User profile"
},
{
id: "3", tag: "Management", title: "Smart Pantry System", description: "Track expiration dates, quantities, and nutritional info. Get notified before ingredients expire and receive smart shopping recommendations.", imageSrc: "https://images.unsplash.com/photo-1556740738-b6a63e27c4df?w=600&q=80&_wi=1", imageAlt: "Smart pantry management"
id: "3", title: "AI Recipe Generation", description: "Enter your ingredients and let AI create personalized recipes just for you. Discover new dishes instantly.", tag: "AI Powered", imageSrc: "https://images.unsplash.com/photo-1498198788026-432a562a71cc?w=600&q=80", imageAlt: "AI technology"
},
{
id: "4", title: "Session Management", description: "Seamless login experience with secure session handling. Your preferences are remembered across devices.", tag: "Always Connected", imageSrc: "https://images.unsplash.com/photo-1460925895917-aeb19be489c7?w=600&q=80", imageAlt: "Connected devices"
},
{
id: "5", title: "Password Recovery", description: "Forgot your password? Our secure recovery system gets you back into your account quickly and safely.", tag: "Peace of Mind", imageSrc: "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=600&q=80", imageAlt: "Recovery process"
},
{
id: "6", title: "Liquid Glass Design", description: "Beautiful, modern UI with glass-morphism effects. Every interaction feels smooth and premium.", tag: "Design", imageSrc: "https://images.unsplash.com/photo-1561070791-2526d30994b5?w=600&q=80", imageAlt: "Modern design"
}
]}
/>
</div>
<div id="download" data-section="download">
<ProductCardThree
products={[
{
id: "1", name: "iOS App", price: "Free", imageSrc: "https://images.unsplash.com/photo-1512941691920-25bda36dc643?w=400&q=80", imageAlt: "EasyRecipes iOS app", initialQuantity: 1
},
{
id: "2", name: "Android App", price: "Free", imageSrc: "https://images.unsplash.com/photo-1526374965328-7f5ae4e8a83f?w=400&q=80", imageAlt: "EasyRecipes Android app", initialQuantity: 1
},
{
id: "3", name: "Web Platform", price: "Free", imageSrc: "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=400&q=80", imageAlt: "EasyRecipes web platform", initialQuantity: 1
}
]}
title="Download EasyRecipes"
description="Available on all your favorite platforms"
tag="Get Started Now"
tagIcon={Zap}
textboxLayout="default"
animationType="slide-up"
title="Powerful Features for Your Kitchen"
description="Everything you need to revolutionize your cooking experience with AI and secure authentication"
textboxLayout="default"
useInvertedBackground={false}
gridVariant="three-columns-all-equal-width"
tag="Features"
/>
</div>
<div id="about" data-section="about">
<TextAbout
tag="Why Choose EasyRecipes"
tagIcon={Heart}
title="Revolutionizing home cooking with AI-powered intelligence, making every meal delicious, sustainable, and personalized to your taste."
useInvertedBackground={true}
buttons={[
{ text: "Learn Our Story", href: "#" }
]}
/>
</div>
<div id="testimonial" data-section="testimonial">
<TestimonialCardFive
testimonials={[
{
id: "1", name: "Jennifer Rodriguez, Nutritionist", date: "Date: 10 Jan 2025", title: "A nutritionist's dream app!", quote: "I recommend EasyRecipes to all my clients. The AI understands nutrition profiles perfectly and suggests balanced, delicious meals every time.", tag: "Healthcare Pro", avatarSrc: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=600&q=80&_wi=2"
},
{
id: "2", name: "David Kim, Sustainability Advocate", date: "Date: 12 Jan 2025", title: "Reduces food waste dramatically!", quote: "This app has cut my food waste by 70%! The smart pantry notifications and AI-suggested recipes use up ingredients before they spoil.", tag: "Eco Warrior", avatarSrc: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=600&q=80&_wi=2"
},
{
id: "3", name: "Lisa Wong, Busy Mom", date: "Date: 14 Jan 2025", title: "Meal planning made easy!", quote: "Between work and kids, I don't have time to plan meals. EasyRecipes does it for me and my family actually enjoys what I cook!", tag: "Parent", avatarSrc: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1556740738-b6a63e27c4df?w=600&q=80&_wi=2"
},
{
id: "4", name: "Michael Chen, Food Blogger", date: "Date: 16 Jan 2025", title: "Creative cooking inspiration!", quote: "The AI suggestions are incredibly creative. I've discovered amazing flavor combinations I never would have thought of on my own!", tag: "Creator", avatarSrc: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1495521821757-a1efb6729352?w=600&q=80&_wi=1"
},
{
id: "5", name: "Amanda Foster, Culinary Student", date: "Date: 18 Jan 2025", title: "Learning tool and cooking companion!", quote: "Perfect for learning! The app explains why certain ingredients work together and teaches me technique while I cook.", tag: "Student Chef", avatarSrc: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=600&q=80&_wi=3"
},
{
id: "6", name: "Robert Martinez, Tech Enthusiast", date: "Date: 20 Jan 2025", title: "Impressive AI technology!", quote: "As a tech person, I'm impressed by how advanced the AI is. It understands context, preferences, and adapts to your cooking style.", tag: "Tech Lover", avatarSrc: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&q=80", imageSrc: "https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=600&q=80&_wi=3"
}
]}
title="What Chefs and Cooking Enthusiasts Say"
description="Join thousands of users discovering new culinary possibilities"
tag="Reviews"
textboxLayout="default"
title="EasyRecipes: Your AI Kitchen Assistant with Secure Authentication"
useInvertedBackground={false}
/>
</div>
<div id="pricing" data-section="pricing">
<PricingCardThree
plans={[
{
id: "1", price: "Free", name: "Recipe Explorer", buttons: [
{ text: "Start Free", href: "/download" }
],
features: [
"50+ AI-generated recipes monthly", "Basic ingredient scanner", "Simple pantry tracking", "Standard recipe recommendations", "Community recipes access"
]
},
{
id: "2", badge: "Most Popular", badgeIcon: Sparkles,
price: "$4.99/mo", name: "Culinary Pro", buttons: [
{ text: "Subscribe Now", href: "/download" }
],
features: [
"Unlimited AI recipe generation", "Advanced ingredient scanner with nutrition", "Smart pantry with expiration alerts", "Personalized meal planning", "Dietary preference customization", "Priority recipe updates"
]
},
{
id: "3", price: "$9.99/mo", name: "Chef Premium", buttons: [
{ text: "Unlock Premium", href: "/download" }
],
features: [
"Everything in Culinary Pro", "Advanced AI personalization", "Exclusive chef-curated recipes", "Nutritional analysis & meal prep", "Recipe scaling & conversions", "Priority support & new features", "Multi-user household access"
]
}
]}
title="Choose Your Cooking Journey"
description="From home chefs to culinary enthusiasts"
tag="Flexible Plans"
textboxLayout="default"
animationType="slide-up"
useInvertedBackground={true}
/>
</div>
<div id="contact" data-section="contact">
<ContactText
text="Transform your cooking today with EasyRecipes. Get personalized AI recipes, smart ingredient management, and culinary inspiration in your pocket. Download now and start creating delicious meals!"
animationType="entrance-slide"
background={{ variant: "radial-gradient" }}
buttons={[
{ text: "Download App", href: "/download" },
{ text: "Contact Support", href: "/contact" }
{ text: "Create Account", href: "/auth" },
{ text: "Try the App", href: "/app" }
]}
useInvertedBackground={false}
/>
</div>
<BlurBottomBackground />
<div id="footer" data-section="footer">
<FooterBaseReveal
<FooterMedia
imageSrc="https://images.unsplash.com/photo-1495521821757-a1efb6729352?w=1920&q=80"
imageAlt="Footer background"
columns={[
{
title: "Product", items: [
{ label: "Features", href: "/features" },
{ label: "Download", href: "/download" },
{ label: "Features", href: "#features" },
{ label: "Pricing", href: "/" },
{ label: "Help Center", href: "#" }
{ label: "Security", href: "/" },
{ label: "FAQ", href: "/" }
]
},
{
title: "Company", items: [
{ label: "About Us", href: "/" },
{ label: "Careers", href: "#" },
{ label: "Blog", href: "#" },
{ label: "Press", href: "#" }
{ label: "Blog", href: "/" },
{ label: "Careers", href: "/" },
{ label: "Press", href: "/" }
]
},
{
title: "Support", items: [
{ label: "Documentation", href: "#" },
{ label: "Community", href: "#" },
{ label: "Privacy Policy", href: "#" },
{ label: "Terms of Service", href: "#" }
{ label: "Documentation", href: "/" },
{ label: "Contact Us", href: "/" },
{ label: "Privacy Policy", href: "/" },
{ label: "Terms of Service", href: "/" }
]
}
]}
copyrightText="© 2025 EasyRecipes | AI-Powered Cooking Made Easy"
logoText="EasyRecipes"
copyrightText="© 2025 EasyRecipes. All rights reserved."
/>
</div>
</ThemeProvider>