Merge version_6 into main #17
88
src/app/api/auth/register/route.ts
Normal file
88
src/app/api/auth/register/route.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const DB_FILE = path.join(process.cwd(), 'data', 'users.json');
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
passwordHash: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
function ensureDbDirectory() {
|
||||
const dir = path.dirname(DB_FILE);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function hashPassword(password: string): string {
|
||||
return crypto.createHash('sha256').update(password).digest('hex');
|
||||
}
|
||||
|
||||
function getUsers(): User[] {
|
||||
try {
|
||||
if (fs.existsSync(DB_FILE)) {
|
||||
const data = fs.readFileSync(DB_FILE, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading users file:', error);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function saveUsers(users: User[]) {
|
||||
ensureDbDirectory();
|
||||
fs.writeFileSync(DB_FILE, JSON.stringify(users, null, 2));
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, email, password } = body;
|
||||
|
||||
if (!name || !email || !password) {
|
||||
return NextResponse.json(
|
||||
{ message: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const users = getUsers();
|
||||
const existingUser = users.find(u => u.email === email);
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ message: 'Email already registered' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
const newUser: User = {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
email,
|
||||
passwordHash: hashPassword(password),
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
users.push(newUser);
|
||||
saveUsers(users);
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: 'User registered successfully', userId: newUser.id },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
return NextResponse.json(
|
||||
{ message: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
168
src/app/auth/login/page.tsx
Normal file
168
src/app/auth/login/page.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import Input from '@/components/form/Input';
|
||||
import ButtonDirectionalHover from '@/components/button/ButtonDirectionalHover/ButtonDirectionalHover';
|
||||
import { Mail, Lock, LogIn } from 'lucide-react';
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.message || "Login failed");
|
||||
}
|
||||
|
||||
router.push("/dashboard");
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="elastic-effect"
|
||||
defaultTextAnimation="entrance-slide"
|
||||
borderRadius="pill"
|
||||
contentWidth="smallMedium"
|
||||
sizing="mediumSizeLargeTitles"
|
||||
background="blurBottom"
|
||||
cardStyle="gradient-bordered"
|
||||
primaryButtonStyle="flat"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="extrabold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Treino", id: "training" },
|
||||
{ name: "Nutrição", id: "nutrition" },
|
||||
]}
|
||||
button={{ text: "Registrar", href: "/auth/register" }}
|
||||
brandName="FitFlow Pro"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-[calc(100vh-200px)] flex items-center justify-center py-20 px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="bg-card rounded-2xl p-8 shadow-lg border border-accent/20">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="flex items-center justify-center mb-4 gap-2">
|
||||
<LogIn className="w-8 h-8 text-primary-cta" />
|
||||
<h1 className="text-3xl font-extrabold text-foreground">Login</h1>
|
||||
</div>
|
||||
<p className="text-foreground/70 text-sm">Acesse sua conta FitFlow Pro</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Email
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
type="email"
|
||||
placeholder="seu.email@exemplo.com"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Senha
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
type="password"
|
||||
placeholder="Sua senha segura"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/30 rounded-lg">
|
||||
<p className="text-red-600 text-sm font-medium">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<ButtonDirectionalHover
|
||||
text={isLoading ? "Entrando..." : "Entrar"}
|
||||
onClick={handleLogin}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-accent/20 text-center">
|
||||
<p className="text-foreground/70 text-sm">
|
||||
Não tem conta?{" "}
|
||||
<a href="/auth/register" className="text-primary-cta hover:text-primary-cta/80 font-semibold">
|
||||
Registre-se
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
columns={[
|
||||
{
|
||||
title: "Produto", items: [
|
||||
{ label: "Dashboard", href: "dashboard" },
|
||||
{ label: "Treino", href: "training" },
|
||||
{ label: "Nutrição", href: "nutrition" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Empresa", items: [
|
||||
{ label: "Sobre", href: "/" },
|
||||
{ label: "Contato", href: "contact" },
|
||||
{ label: "Privacidade", href: "privacy" },
|
||||
]
|
||||
}
|
||||
]}
|
||||
logoText="FitFlow Pro"
|
||||
copyrightText="© 2025 FitFlow Pro. Todos os direitos reservados."
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
212
src/app/auth/register/page.tsx
Normal file
212
src/app/auth/register/page.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import Input from '@/components/form/Input';
|
||||
import ButtonDirectionalHover from '@/components/button/ButtonDirectionalHover/ButtonDirectionalHover';
|
||||
import { Mail, Lock, User, UserPlus } from 'lucide-react';
|
||||
|
||||
export default function RegisterPage() {
|
||||
const router = useRouter();
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleRegister = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError("Senhas não correspondem");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/register", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.message || "Registration failed");
|
||||
}
|
||||
|
||||
router.push("/auth/login?registered=true");
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="elastic-effect"
|
||||
defaultTextAnimation="entrance-slide"
|
||||
borderRadius="pill"
|
||||
contentWidth="smallMedium"
|
||||
sizing="mediumSizeLargeTitles"
|
||||
background="blurBottom"
|
||||
cardStyle="gradient-bordered"
|
||||
primaryButtonStyle="flat"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="extrabold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Treino", id: "training" },
|
||||
{ name: "Nutrição", id: "nutrition" },
|
||||
]}
|
||||
button={{ text: "Login", href: "/auth/login" }}
|
||||
brandName="FitFlow Pro"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-[calc(100vh-200px)] flex items-center justify-center py-20 px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="bg-card rounded-2xl p-8 shadow-lg border border-accent/20">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="flex items-center justify-center mb-4 gap-2">
|
||||
<UserPlus className="w-8 h-8 text-primary-cta" />
|
||||
<h1 className="text-3xl font-extrabold text-foreground">Registrar</h1>
|
||||
</div>
|
||||
<p className="text-foreground/70 text-sm">Crie sua conta FitFlow Pro</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleRegister} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Nome Completo
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={name}
|
||||
onChange={setName}
|
||||
type="text"
|
||||
placeholder="Seu nome completo"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Email
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
type="email"
|
||||
placeholder="seu.email@exemplo.com"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Senha
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
type="password"
|
||||
placeholder="Sua senha segura"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Confirmar Senha
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-3 w-5 h-5 text-accent/50" />
|
||||
<Input
|
||||
value={confirmPassword}
|
||||
onChange={setConfirmPassword}
|
||||
type="password"
|
||||
placeholder="Confirme sua senha"
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/30 rounded-lg">
|
||||
<p className="text-red-600 text-sm font-medium">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<ButtonDirectionalHover
|
||||
text={isLoading ? "Registrando..." : "Criar Conta"}
|
||||
onClick={handleRegister}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-accent/20 text-center">
|
||||
<p className="text-foreground/70 text-sm">
|
||||
Já tem conta?{" "}
|
||||
<a href="/auth/login" className="text-primary-cta hover:text-primary-cta/80 font-semibold">
|
||||
Faça login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
columns={[
|
||||
{
|
||||
title: "Produto", items: [
|
||||
{ label: "Dashboard", href: "dashboard" },
|
||||
{ label: "Treino", href: "training" },
|
||||
{ label: "Nutrição", href: "nutrition" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Empresa", items: [
|
||||
{ label: "Sobre", href: "/" },
|
||||
{ label: "Contato", href: "contact" },
|
||||
{ label: "Privacidade", href: "privacy" },
|
||||
]
|
||||
}
|
||||
]}
|
||||
logoText="FitFlow Pro"
|
||||
copyrightText="© 2025 FitFlow Pro. Todos os direitos reservados."
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -34,8 +34,9 @@ export default function LandingPage() {
|
||||
{ name: "Dashboard", id: "dashboard" },
|
||||
{ name: "Treino", id: "training" },
|
||||
{ name: "Nutrição", id: "nutrition" },
|
||||
{ name: "Receitas", id: "/recipes" },
|
||||
{ name: "Comunidade", id: "community" },
|
||||
{ name: "Perfil", id: "onboarding" }
|
||||
{ name: "Perfil", id: "profile" }
|
||||
]}
|
||||
button={{ text: "Começar Agora", href: "contact" }}
|
||||
brandName="FitFlow Pro"
|
||||
@@ -212,12 +213,14 @@ export default function LandingPage() {
|
||||
tagIcon={Apple}
|
||||
plans={[
|
||||
{
|
||||
id: "deficit", badge: "Perda de Peso", price: "Déficit Calórico", subtitle: "Receitas otimizadas para queima de calorias", features: [
|
||||
id: "deficit", badge: "Perda de Peso", badgeIcon: TrendingDown,
|
||||
price: "Déficit Calórico", subtitle: "Receitas otimizadas para queima de calorias", features: [
|
||||
"Macros calculados automaticamente", "Prep time entre 15-30 min", "Proteína alta, carboidrato estratégico", "Rastreamento integrado", "Sugestões diárias personalizadas"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "surplus", badge: "Ganho de Massa", price: "Superávit Estratégico", subtitle: "Nutrição para crescimento muscular", features: [
|
||||
id: "surplus", badge: "Ganho de Massa", badgeIcon: TrendingUp,
|
||||
price: "Superávit Estratégico", subtitle: "Nutrição para crescimento muscular", features: [
|
||||
"Calorias calculadas para ganho", "Proteína máxima (2g por kg)", "Carbos pré e pós-treino", "Tempo de preparo eficiente", "Sincronizado com treino de força"
|
||||
]
|
||||
}
|
||||
@@ -260,16 +263,16 @@ export default function LandingPage() {
|
||||
tagIcon={Users}
|
||||
members={[
|
||||
{
|
||||
id: "1", name: "Marcus A.", role: "Elite Runner", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=1"
|
||||
id: "1", name: "Marcus A.", role: "Elite Runner", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=1", imageAlt: "Marcus A."
|
||||
},
|
||||
{
|
||||
id: "2", name: "Ana L.", role: "Strength Coach", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=1"
|
||||
id: "2", name: "Ana L.", role: "Strength Coach", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=1", imageAlt: "Ana L."
|
||||
},
|
||||
{
|
||||
id: "3", name: "Rafael S.", role: "Nutrition Expert", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=1"
|
||||
id: "3", name: "Rafael S.", role: "Nutrition Expert", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=1", imageAlt: "Rafael S."
|
||||
},
|
||||
{
|
||||
id: "4", name: "Sofia M.", role: "Fitness Trainer", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=1"
|
||||
id: "4", name: "Sofia M.", role: "Fitness Trainer", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=1", imageAlt: "Sofia M."
|
||||
}
|
||||
]}
|
||||
animationType="blur-reveal"
|
||||
@@ -283,22 +286,22 @@ export default function LandingPage() {
|
||||
<TestimonialCardTwelve
|
||||
testimonials={[
|
||||
{
|
||||
id: "1", name: "Carlos M.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=2"
|
||||
id: "1", name: "Carlos M.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/professional-athlete-portrait-male-fitne-1773256979726-5009f852.png?_wi=2", imageAlt: "Carlos M."
|
||||
},
|
||||
{
|
||||
id: "2", name: "Beatriz R.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=2"
|
||||
id: "2", name: "Beatriz R.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/fit-female-athlete-portrait-determined-e-1773256980310-c05dce2f.png?_wi=2", imageAlt: "Beatriz R."
|
||||
},
|
||||
{
|
||||
id: "3", name: "Diego P.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=2"
|
||||
id: "3", name: "Diego P.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/athletic-male-trainer-portrait-confident-1773256979906-c5e05a88.png?_wi=2", imageAlt: "Diego P."
|
||||
},
|
||||
{
|
||||
id: "4", name: "Juliana T.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=2"
|
||||
id: "4", name: "Juliana T.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-fitness-coach-portrait-profession-1773256979710-97e8b5fe.png?_wi=2", imageAlt: "Juliana T."
|
||||
},
|
||||
{
|
||||
id: "5", name: "Lucas F.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/young-athlete-male-portrait-energetic-ex-1773256982698-63e4e494.png"
|
||||
id: "5", name: "Lucas F.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/young-athlete-male-portrait-energetic-ex-1773256982698-63e4e494.png", imageAlt: "Lucas F."
|
||||
},
|
||||
{
|
||||
id: "6", name: "Marina S.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png"
|
||||
id: "6", name: "Marina S.", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png", imageAlt: "Marina S."
|
||||
}
|
||||
]}
|
||||
cardTitle="Mais de 150 mil atletas já transformaram seu corpo com FitFlow Pro"
|
||||
@@ -354,13 +357,13 @@ export default function LandingPage() {
|
||||
{ label: "Dashboard", href: "dashboard" },
|
||||
{ label: "Treino", href: "training" },
|
||||
{ label: "Nutrição", href: "nutrition" },
|
||||
{ label: "Cardio Hub", href: "cardio" }
|
||||
{ label: "Receitas", href: "/recipes" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Comunidade", items: [
|
||||
{ label: "Comunidade", href: "community" },
|
||||
{ label: "Perfil", href: "onboarding" },
|
||||
{ label: "Perfil", href: "profile" },
|
||||
{ label: "Rankings", href: "rankings" },
|
||||
{ label: "Blog", href: "blog" }
|
||||
]
|
||||
|
||||
275
src/app/recipes/page.tsx
Normal file
275
src/app/recipes/page.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered';
|
||||
import HeroSplitKpi from '@/components/sections/hero/HeroSplitKpi';
|
||||
import ProductCardTwo from '@/components/sections/product/ProductCardTwo';
|
||||
import ContactSplit from '@/components/sections/contact/ContactSplit';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import { useState } from 'react';
|
||||
import { Flame, Target, TrendingDown, TrendingUp, Mail } from 'lucide-react';
|
||||
|
||||
interface Recipe {
|
||||
id: string;
|
||||
brand: string;
|
||||
name: string;
|
||||
price: string;
|
||||
rating: number;
|
||||
reviewCount: string;
|
||||
imageSrc: string;
|
||||
imageAlt: string;
|
||||
goal: 'weight-loss' | 'muscle-gain';
|
||||
}
|
||||
|
||||
const recipes: Recipe[] = [
|
||||
{
|
||||
id: '1',
|
||||
brand: 'Deficit Smart',
|
||||
name: 'Grilled Chicken Salad Bowl',
|
||||
price: '380 cal',
|
||||
rating: 5,
|
||||
reviewCount: '2.3k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/ultra-modern-fitness-app-dashboard-with--1773256981295-f56c580b.png',
|
||||
imageAlt: 'Grilled Chicken Salad',
|
||||
goal: 'weight-loss'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
brand: 'Lean Protein',
|
||||
name: 'Egg White Omelette',
|
||||
price: '250 cal',
|
||||
rating: 4,
|
||||
reviewCount: '1.8k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png',
|
||||
imageAlt: 'Egg White Omelette',
|
||||
goal: 'weight-loss'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
brand: 'Surplus Gains',
|
||||
name: 'Protein Pasta with Salmon',
|
||||
price: '650 cal',
|
||||
rating: 5,
|
||||
reviewCount: '3.1k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/premium-onboarding-screen-for-fitness-ap-1773256981180-774b293c.png',
|
||||
imageAlt: 'Protein Pasta with Salmon',
|
||||
goal: 'muscle-gain'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
brand: 'Bulk Smart',
|
||||
name: 'Rice & Chicken Combo',
|
||||
price: '720 cal',
|
||||
rating: 5,
|
||||
reviewCount: '2.9k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/performance-metrics-showcase-displaying--1773256982260-f9a5cff0.png',
|
||||
imageAlt: 'Rice & Chicken Combo',
|
||||
goal: 'muscle-gain'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
brand: 'Low Cal',
|
||||
name: 'Zucchini Noodles',
|
||||
price: '150 cal',
|
||||
rating: 4,
|
||||
reviewCount: '1.2k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/gps-running-tracker-interface-with-real--1773256980694-2abe167e.png',
|
||||
imageAlt: 'Zucchini Noodles',
|
||||
goal: 'weight-loss'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
brand: 'Muscle Builder',
|
||||
name: 'Sweet Potato & Steak',
|
||||
price: '800 cal',
|
||||
rating: 5,
|
||||
reviewCount: '2.7k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/interactive-anatomical-body-model-showin-1773256980448-3cccd7b3.png',
|
||||
imageAlt: 'Sweet Potato & Steak',
|
||||
goal: 'muscle-gain'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
brand: 'Keto Smart',
|
||||
name: 'Avocado & Salmon Bowl',
|
||||
price: '420 cal',
|
||||
rating: 4,
|
||||
reviewCount: '1.9k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/workout-execution-interface-showing-set--1773256980664-da11c464.png',
|
||||
imageAlt: 'Avocado & Salmon Bowl',
|
||||
goal: 'weight-loss'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
brand: 'Protein Max',
|
||||
name: 'Greek Yogurt Parfait',
|
||||
price: '580 cal',
|
||||
rating: 5,
|
||||
reviewCount: '2.5k',
|
||||
imageSrc: 'https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/female-athlete-portrait-fit-build-profes-1773256980134-0faaa8fa.png',
|
||||
imageAlt: 'Greek Yogurt Parfait',
|
||||
goal: 'muscle-gain'
|
||||
}
|
||||
];
|
||||
|
||||
export default function RecipesPage() {
|
||||
const [activeGoal, setActiveGoal] = useState<'weight-loss' | 'muscle-gain' | 'all'>('all');
|
||||
|
||||
const filteredRecipes = activeGoal === 'all'
|
||||
? recipes
|
||||
: recipes.filter(recipe => recipe.goal === activeGoal);
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="elastic-effect"
|
||||
defaultTextAnimation="entrance-slide"
|
||||
borderRadius="pill"
|
||||
contentWidth="smallMedium"
|
||||
sizing="mediumSizeLargeTitles"
|
||||
background="blurBottom"
|
||||
cardStyle="gradient-bordered"
|
||||
primaryButtonStyle="flat"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="extrabold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
navItems={[
|
||||
{ name: "Dashboard", id: "dashboard" },
|
||||
{ name: "Treino", id: "training" },
|
||||
{ name: "Nutrição", id: "nutrition" },
|
||||
{ name: "Receitas", id: "/recipes" },
|
||||
{ name: "Comunidade", id: "community" },
|
||||
{ name: "Perfil", id: "profile" }
|
||||
]}
|
||||
button={{ text: "Começar Agora", href: "contact" }}
|
||||
brandName="FitFlow Pro"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="hero" data-section="hero">
|
||||
<HeroSplitKpi
|
||||
title="Receitas Inteligentes para Seu Objetivo"
|
||||
description="Explore receitas selecionadas especificamente para suas metas de fitness. Filtro por objetivo de perda de peso ou ganho de massa muscular. Cada receita inclui macros calculados, tempo de preparo e recomendações nutricionais."
|
||||
tag="Receituário Inteligente"
|
||||
tagIcon={Flame}
|
||||
background={{ variant: "glowing-orb" }}
|
||||
kpis={[
|
||||
{ value: "500+", label: "Receitas" },
|
||||
{ value: "30min", label: "Prep Time Médio" },
|
||||
{ value: "4.8★", label: "Avaliação" }
|
||||
]}
|
||||
enableKpiAnimation={true}
|
||||
imageSrc="https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png"
|
||||
imageAlt="Receitas inteligentes"
|
||||
imagePosition="right"
|
||||
mediaAnimation="slide-up"
|
||||
buttons={[
|
||||
{ text: "Explorar Receitas", href: "#recipes" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="recipes" data-section="recipes" className="py-16 md:py-24">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-2xl md:text-4xl font-bold mb-6">Filtrar por Objetivo</h2>
|
||||
<div className="flex flex-wrap gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveGoal('all')}
|
||||
className={`px-6 py-3 rounded-full font-semibold transition-all duration-300 ${
|
||||
activeGoal === 'all'
|
||||
? 'bg-primary-cta text-white shadow-lg'
|
||||
: 'bg-secondary-cta bg-opacity-50 hover:bg-opacity-100'
|
||||
}`}
|
||||
>
|
||||
Todas as Receitas
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveGoal('weight-loss')}
|
||||
className={`px-6 py-3 rounded-full font-semibold transition-all duration-300 flex items-center gap-2 ${
|
||||
activeGoal === 'weight-loss'
|
||||
? 'bg-primary-cta text-white shadow-lg'
|
||||
: 'bg-secondary-cta bg-opacity-50 hover:bg-opacity-100'
|
||||
}`}
|
||||
>
|
||||
<TrendingDown className="w-4 h-4" />
|
||||
Perda de Peso
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveGoal('muscle-gain')}
|
||||
className={`px-6 py-3 rounded-full font-semibold transition-all duration-300 flex items-center gap-2 ${
|
||||
activeGoal === 'muscle-gain'
|
||||
? 'bg-primary-cta text-white shadow-lg'
|
||||
: 'bg-secondary-cta bg-opacity-50 hover:bg-opacity-100'
|
||||
}`}
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Ganho de Massa
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProductCardTwo
|
||||
products={filteredRecipes}
|
||||
animationType="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
gridVariant="three-columns-all-equal-width"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="contact" data-section="contact">
|
||||
<ContactSplit
|
||||
tag="Dica Nutricional"
|
||||
tagIcon={Mail}
|
||||
title="Receba Receitas Personalizadas"
|
||||
description="Inscreva-se para receber receitas semanais personalizadas baseadas em suas metas e preferências alimentares. Nossos nutricionistas selecionam as melhores opções para você."
|
||||
background={{ variant: "sparkles-gradient" }}
|
||||
useInvertedBackground={false}
|
||||
imageSrc="https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AoRNSPr0mCBj85JKsHl7qxTHsl/nutrition-dashboard-showing-meal-plans-d-1773256981349-9348b6d9.png"
|
||||
imageAlt="Receitas personalizadas"
|
||||
mediaAnimation="slide-up"
|
||||
mediaPosition="right"
|
||||
inputPlaceholder="seu.email@exemplo.com"
|
||||
buttonText="Inscrever-se"
|
||||
termsText="Você receberá sugestões de receitas toda segunda-feira. Sem spam, promessa."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
columns={[
|
||||
{
|
||||
title: "Produto", items: [
|
||||
{ label: "Dashboard", href: "dashboard" },
|
||||
{ label: "Treino", href: "training" },
|
||||
{ label: "Nutrição", href: "nutrition" },
|
||||
{ label: "Receitas", href: "/recipes" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Comunidade", items: [
|
||||
{ label: "Comunidade", href: "community" },
|
||||
{ label: "Perfil", href: "profile" },
|
||||
{ label: "Rankings", href: "rankings" },
|
||||
{ label: "Blog", href: "blog" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Empresa", items: [
|
||||
{ label: "Sobre", href: "about" },
|
||||
{ label: "Contato", href: "contact" },
|
||||
{ label: "Privacidade", href: "privacy" },
|
||||
{ label: "Termos", href: "terms" }
|
||||
]
|
||||
}
|
||||
]}
|
||||
logoText="FitFlow Pro"
|
||||
copyrightText="© 2025 FitFlow Pro. Todos os direitos reservados."
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user