From bcaa0caf27bd66a702e58b146e7f1739f4fa8c0c Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:05 +0000 Subject: [PATCH 01/89] Update src/app/login/page.tsx --- src/app/login/page.tsx | 266 ++++++++++++++++++++++++++++------------- 1 file changed, 180 insertions(+), 86 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 7a1bd0a..6bf040a 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,47 +1,60 @@ "use client"; +import { useState } from "react"; import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; -import { useState } from "react"; -import { Eye, EyeOff, Mail, Lock } from 'lucide-react'; +import FooterBase from '@/components/sections/footer/FooterBase'; import Input from '@/components/form/Input'; +import ButtonElasticEffect from '@/components/button/ButtonElasticEffect/ButtonElasticEffect'; +import { LogIn, Mail, Lock, Eye, EyeOff } from 'lucide-react'; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); - const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); - const [isSubmitted, setIsSubmitted] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + const [rememberMe, setRememberMe] = useState(false); - const validateForm = () => { - const newErrors: { email?: string; password?: string } = {}; - - if (!email) { - newErrors.email = "Email é obrigatório"; - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { - newErrors.email = "Email inválido"; - } - - if (!password) { - newErrors.password = "Senha é obrigatória"; - } else if (password.length < 6) { - newErrors.password = "Senha deve ter no mínimo 6 caracteres"; - } - - return newErrors; - }; - - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - const newErrors = validateForm(); - setErrors(newErrors); - - if (Object.keys(newErrors).length === 0) { - setIsSubmitted(true); - console.log("Login attempt:", { email, password }); - setTimeout(() => { - setIsSubmitted(false); - }, 2000); + setError(""); + setIsLoading(true); + + try { + // Simulate API call + if (!email || !password) { + setError("Por favor, preencha todos os campos."); + return; + } + + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + setError("Por favor, insira um email válido."); + return; + } + + if (password.length < 6) { + setError("A senha deve ter no mínimo 6 caracteres."); + return; + } + + // Simulate successful login + const response = { success: true, token: "user_token_123" }; + + if (response.success) { + // Store session + sessionStorage.setItem("authToken", response.token); + sessionStorage.setItem("userEmail", email); + if (rememberMe) { + localStorage.setItem("userEmail", email); + } + // Redirect to dashboard + window.location.href = "/dashboard"; + } + } catch (err) { + setError("Erro ao fazer login. Tente novamente."); + } finally { + setIsLoading(false); } }; @@ -61,100 +74,181 @@ export default function LoginPage() { -
+
-
-
-

Bem-vindo de Volta

-

Faça login para acessar seu progresso

+
+ {/* Header */} +
+
+
+ +
+
+

+ Bem-vindo de Volta +

+

+ Faça login na sua conta FitFlow Pro +

-
-
- - - {errors.email && ( -

{errors.email}

- )} + {/* Error Message */} + {error && ( +
+ {error}
+ )} -
-
+ + {/* Submit Button */} + - {isSubmitted ? "Entrando..." : "Entrar"} - + className="w-full" + /> -
-

- Não tem conta?{" "} - - Criar conta - -

+ {/* Divider */} +
+
+ ou +
-
-

Teste gratuito por 30 dias. Sem cartão de crédito necessário.

+ {/* Social Login */} +
+ + +
+ + {/* Sign Up Link */} +
+ Não tem conta?{" "} + + Inscreva-se aqui +
+ + {/* Security Note */} +

+ Sua senha está sempre protegida. Nunca compartilhamos seus dados com terceiros. +

+ + ); } -- 2.49.1 From f9d8b78ee5fab5e4415471d280acc640a4605526 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:06 +0000 Subject: [PATCH 02/89] Add src/app/onboarding/page.tsx --- src/app/onboarding/page.tsx | 339 ++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 src/app/onboarding/page.tsx diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx new file mode 100644 index 0000000..909484e --- /dev/null +++ b/src/app/onboarding/page.tsx @@ -0,0 +1,339 @@ +"use client"; + +import { useState } from "react"; +import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; +import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; +import FooterBase from '@/components/sections/footer/FooterBase'; +import { ChevronRight, User, Heart } from 'lucide-react'; + +interface ProfileData { + name: string; + gender: string; + height: string; + weight: string; + age: string; +} + +const GENDERS = ['Masculino', 'Feminino', 'Outro']; + +export default function OnboardingPage() { + const [step, setStep] = useState<'biometric' | 'metrics'>('biometric'); + const [formData, setFormData] = useState({ + name: '', + gender: '', + height: '', + weight: '', + age: '' + }); + const [savedProfile, setSavedProfile] = useState(null); + + const handleBiometricChange = (field: keyof Omit, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleMetricsChange = (field: 'height' | 'weight' | 'age', value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleBiometricSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (formData.name && formData.gender) { + setStep('metrics'); + } + }; + + const handleMetricsSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (formData.height && formData.weight && formData.age) { + // Save to localStorage + localStorage.setItem('userProfile', JSON.stringify(formData)); + setSavedProfile(formData); + setTimeout(() => { + window.location.href = '/'; + }, 2000); + } + }; + + const handleReset = () => { + setStep('biometric'); + setFormData({ name: '', gender: '', height: '', weight: '', age: '' }); + setSavedProfile(null); + }; + + return ( + + + +
+
+ {!savedProfile ? ( +
+ {/* Header */} +
+
+ +
+

+ {step === 'biometric' ? 'Criar Perfil' : 'Dados Físicos'} +

+

+ {step === 'biometric' + ? 'Comece preenchendo seus dados básicos' + : 'Agora adicione suas medidas'} +

+
+ + {/* Progress Indicator */} +
+
+
+
+ + {/* Form Container */} +
+ {step === 'biometric' && ( +
+ {/* Name Field */} +
+ + handleBiometricChange('name', e.target.value)} + placeholder="Ex: João Silva" + className="w-full px-4 py-3 bg-background border border-accent/30 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-cta focus:border-transparent transition-all text-foreground placeholder:text-foreground/50" + required + /> +
+ + {/* Gender Field */} +
+ +
+ {GENDERS.map((g) => ( + + ))} +
+
+ + {/* Submit Button */} + +
+ )} + + {step === 'metrics' && ( +
+ {/* Height Field */} +
+ + handleMetricsChange('height', e.target.value)} + placeholder="Ex: 175" + min="100" + max="250" + className="w-full px-4 py-3 bg-background border border-accent/30 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-cta focus:border-transparent transition-all text-foreground placeholder:text-foreground/50" + required + /> +
+ + {/* Weight Field */} +
+ + handleMetricsChange('weight', e.target.value)} + placeholder="Ex: 75" + min="20" + max="300" + step="0.1" + className="w-full px-4 py-3 bg-background border border-accent/30 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-cta focus:border-transparent transition-all text-foreground placeholder:text-foreground/50" + required + /> +
+ + {/* Age Field */} +
+ + handleMetricsChange('age', e.target.value)} + placeholder="Ex: 28" + min="13" + max="120" + className="w-full px-4 py-3 bg-background border border-accent/30 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-cta focus:border-transparent transition-all text-foreground placeholder:text-foreground/50" + required + /> +
+ + {/* Button Group */} +
+ + +
+
+ )} +
+ + {/* Form Info */} +
+

+ Seus dados são salvos localmente e podem ser editados depois +

+
+
+ ) : ( + /* Success State */ +
+
+
+ +
+

Perfil Criado!

+

+ Bem-vindo, {savedProfile.name}! +

+
+ + {/* Profile Summary */} +
+
+ Gênero: + {savedProfile.gender} +
+
+ Altura: + {savedProfile.height} cm +
+
+ Peso: + {savedProfile.weight} kg +
+
+ Idade: + {savedProfile.age} anos +
+
+ +

+ Redirecionando para o dashboard em breve... +

+ + +
+ )} +
+
+ + +
+ ); +} -- 2.49.1 From f54f9f9bf8b577a36b6f93ba174484f156c2ee0f Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:06 +0000 Subject: [PATCH 03/89] Update src/app/page.tsx --- src/app/page.tsx | 713 +++++++++++++++++++++++------------------------ 1 file changed, 356 insertions(+), 357 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 3120059..7629c5f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,377 +10,376 @@ import MetricCardFourteen from '@/components/sections/metrics/MetricCardFourteen import TeamCardOne from '@/components/sections/team/TeamCardOne'; import TestimonialCardTwelve from '@/components/sections/testimonial/TestimonialCardTwelve'; import SocialProofOne from '@/components/sections/socialProof/SocialProofOne'; -import ContactText from '@/components/sections/contact/ContactText'; -import FooterLogoEmphasis from '@/components/sections/footer/FooterLogoEmphasis'; -import { Activity, Apple, Brain, Dumbbell, Heart, Target, Zap, Users, Star, TrendingDown, TrendingUp } from 'lucide-react'; -import { WorkoutDataIntegration } from '@/app/components/WorkoutDataIntegration'; +import ContactSplit from '@/components/sections/contact/ContactSplit'; +import FooterBase from '@/components/sections/footer/FooterBase'; +import { Activity, Apple, Brain, Dumbbell, Heart, Target, Zap, Users, Star, TrendingDown, TrendingUp, Mail } from 'lucide-react'; export default function LandingPage() { return ( - - - + + -
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
- -
-
+ + ); } -- 2.49.1 From 510d761391b8219d83910ad325b51dd07e155bc6 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:07 +0000 Subject: [PATCH 04/89] Add src/context/AuthContext.tsx --- src/context/AuthContext.tsx | 136 ++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/context/AuthContext.tsx diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..dcfcdcd --- /dev/null +++ b/src/context/AuthContext.tsx @@ -0,0 +1,136 @@ +"use client"; + +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { AuthSession, authUtils } from '@/utils/auth'; + +interface AuthContextType { + session: AuthSession | null; + isLoading: boolean; + isAuthenticated: boolean; + login: (email: string, password: string, rememberMe?: boolean) => Promise; + logout: () => void; + signup: (email: string, password: string) => Promise; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [session, setSession] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + // Initialize session on mount + useEffect(() => { + const currentSession = authUtils.getSession(); + if (currentSession && authUtils.isAuthenticated()) { + setSession(currentSession); + } + setIsLoading(false); + }, []); + + const login = async (email: string, password: string, rememberMe = false) => { + setIsLoading(true); + try { + // Validate inputs + if (!authUtils.isValidEmail(email)) { + throw new Error('Email inválido'); + } + if (!authUtils.isValidPassword(password)) { + throw new Error('Senha deve ter no mínimo 6 caracteres'); + } + + // Simulate API call + const response = await new Promise<{ success: boolean; token: string; userId: string }>((resolve) => { + setTimeout(() => { + resolve({ + success: true, + token: 'user_token_' + Date.now(), + userId: 'user_' + Math.random().toString(36).substr(2, 9), + }); + }, 500); + }); + + if (response.success) { + const newSession: AuthSession = { + token: response.token, + email, + userId: response.userId, + expiresAt: new Date().getTime() + 24 * 60 * 60 * 1000, // 24 hours + }; + + authUtils.setSession(newSession); + if (rememberMe) { + authUtils.setRememberMe(email); + } + setSession(newSession); + } + } finally { + setIsLoading(false); + } + }; + + const logout = () => { + authUtils.clearSession(); + setSession(null); + }; + + const signup = async (email: string, password: string) => { + setIsLoading(true); + try { + // Validate inputs + if (!authUtils.isValidEmail(email)) { + throw new Error('Email inválido'); + } + if (!authUtils.isValidPassword(password)) { + throw new Error('Senha deve ter no mínimo 6 caracteres'); + } + + // Simulate API call + const response = await new Promise<{ success: boolean; token: string; userId: string }>((resolve) => { + setTimeout(() => { + resolve({ + success: true, + token: 'user_token_' + Date.now(), + userId: 'user_' + Math.random().toString(36).substr(2, 9), + }); + }, 500); + }); + + if (response.success) { + const newSession: AuthSession = { + token: response.token, + email, + userId: response.userId, + expiresAt: new Date().getTime() + 24 * 60 * 60 * 1000, + }; + + authUtils.setSession(newSession); + setSession(newSession); + } + } finally { + setIsLoading(false); + } + }; + + const value: AuthContextType = { + session, + isLoading, + isAuthenticated: session !== null && authUtils.isAuthenticated(), + login, + logout, + signup, + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; -- 2.49.1 From 96a8f5247aef20982a68617fdcb6a776558f9119 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:07 +0000 Subject: [PATCH 05/89] Add src/hooks/useAuthGuard.ts --- src/hooks/useAuthGuard.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/hooks/useAuthGuard.ts diff --git a/src/hooks/useAuthGuard.ts b/src/hooks/useAuthGuard.ts new file mode 100644 index 0000000..2e08323 --- /dev/null +++ b/src/hooks/useAuthGuard.ts @@ -0,0 +1,18 @@ +"use client"; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/context/AuthContext'; + +export const useAuthGuard = () => { + const router = useRouter(); + const { isAuthenticated, isLoading } = useAuth(); + + useEffect(() => { + if (!isLoading && !isAuthenticated) { + router.push('/login'); + } + }, [isAuthenticated, isLoading, router]); + + return { isAuthenticated, isLoading }; +}; -- 2.49.1 From b8135f1fc0189a19a4e7e07232b0720ec558ed0d Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:51:07 +0000 Subject: [PATCH 06/89] Add src/utils/auth.ts --- src/utils/auth.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/utils/auth.ts diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..0def36d --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,89 @@ +// Authentication utility functions + +export interface AuthSession { + token: string; + email: string; + userId?: string; + expiresAt?: number; +} + +export const authUtils = { + // Store session in sessionStorage + setSession: (session: AuthSession) => { + sessionStorage.setItem('authToken', session.token); + sessionStorage.setItem('userEmail', session.email); + if (session.userId) { + sessionStorage.setItem('userId', session.userId); + } + if (session.expiresAt) { + sessionStorage.setItem('expiresAt', session.expiresAt.toString()); + } + }, + + // Get current session from sessionStorage + getSession: (): AuthSession | null => { + const token = sessionStorage.getItem('authToken'); + const email = sessionStorage.getItem('userEmail'); + + if (!token || !email) { + return null; + } + + return { + token, + email, + userId: sessionStorage.getItem('userId') || undefined, + expiresAt: sessionStorage.getItem('expiresAt') + ? parseInt(sessionStorage.getItem('expiresAt')!) + : undefined, + }; + }, + + // Check if user is authenticated + isAuthenticated: (): boolean => { + const session = authUtils.getSession(); + if (!session) return false; + + // Check if session has expired + if (session.expiresAt && new Date().getTime() > session.expiresAt) { + authUtils.clearSession(); + return false; + } + + return true; + }, + + // Clear session on logout + clearSession: () => { + sessionStorage.removeItem('authToken'); + sessionStorage.removeItem('userEmail'); + sessionStorage.removeItem('userId'); + sessionStorage.removeItem('expiresAt'); + }, + + // Persist remember-me preference + setRememberMe: (email: string) => { + localStorage.setItem('rememberEmail', email); + }, + + // Get remembered email + getRememberedEmail: (): string | null => { + return localStorage.getItem('rememberEmail'); + }, + + // Clear remember-me preference + clearRememberMe: () => { + localStorage.removeItem('rememberEmail'); + }, + + // Validate email format + isValidEmail: (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }, + + // Validate password strength + isValidPassword: (password: string): boolean => { + return password.length >= 6; + }, +}; -- 2.49.1 From b515f2474988213ea0ae4f2020ea58bf75cdf8f4 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:50 +0000 Subject: [PATCH 07/89] Update src/app/lib/storage/workoutStorage.ts --- src/app/lib/storage/workoutStorage.ts | 358 ++++++-------------------- 1 file changed, 74 insertions(+), 284 deletions(-) diff --git a/src/app/lib/storage/workoutStorage.ts b/src/app/lib/storage/workoutStorage.ts index bfdb2ea..ed263c0 100644 --- a/src/app/lib/storage/workoutStorage.ts +++ b/src/app/lib/storage/workoutStorage.ts @@ -1,309 +1,99 @@ -/** - * Workout and metrics data persistence layer - * Handles saving and retrieving workout data from localStorage - */ - -export interface WorkoutSet { - reps: number; - weight: number; - timestamp: number; -} +// This file contains workout storage utilities export interface WorkoutSession { id: string; - exerciseName: string; date: string; - sets: WorkoutSet[]; - duration: number; // in seconds - caloriesBurned: number; - notes?: string; + duration: number; + exercises: Exercise[]; + totalCalories: number; } -export interface CardioSession { +export interface Exercise { id: string; - type: 'running' | 'walking' | 'cycling'; - date: string; - distance: number; // in km - duration: number; // in seconds - caloriesBurned: number; - pace: number; // km/h - steps?: number; - route?: string; + name: string; + sets: number; + reps: number; + weight: number; } export interface NutritionLog { id: string; date: string; + meals: Meal[]; + totalCalories: number; +} + +export interface Meal { + id: string; + name: string; calories: number; - protein: number; - carbs: number; - fats: number; - meals: string[]; + macros: { + protein: number; + carbs: number; + fats: number; + }; } -export interface UserMetrics { - totalWorkouts: number; - totalCardioDistance: number; - totalCaloriesBurned: number; - currentStreak: number; - personalRecords: Record; - lastUpdated: number; -} +export const workoutStorage = { + // Store workout session + saveWorkout: (session: WorkoutSession): void => { + const workouts = JSON.parse(localStorage.getItem('workouts') || '[]'); + workouts.push(session); + localStorage.setItem('workouts', JSON.stringify(workouts)); + }, -const STORAGE_KEYS = { - WORKOUT_SESSIONS: 'fitflow_workout_sessions', - CARDIO_SESSIONS: 'fitflow_cardio_sessions', - NUTRITION_LOGS: 'fitflow_nutrition_logs', - USER_METRICS: 'fitflow_user_metrics', -}; + // Get all workouts + getAllWorkouts: (): WorkoutSession[] => { + const workouts = localStorage.getItem('workouts'); + return workouts ? JSON.parse(workouts) : []; + }, -/** - * Save a workout session to localStorage - */ -export const saveWorkoutSession = (session: WorkoutSession): void => { - if (typeof window === 'undefined') return; - - try { - const sessions = getWorkoutSessions(); - sessions.push(session); - localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(sessions)); - updateUserMetrics(); - } catch (error) { - console.error('Error saving workout session:', error); - } -}; - -/** - * Get all workout sessions from localStorage - */ -export const getWorkoutSessions = (): WorkoutSession[] => { - if (typeof window === 'undefined') return []; - - try { - const data = localStorage.getItem(STORAGE_KEYS.WORKOUT_SESSIONS); - return data ? JSON.parse(data) : []; - } catch (error) { - console.error('Error retrieving workout sessions:', error); - return []; - } -}; - -/** - * Save a cardio session to localStorage - */ -export const saveCardioSession = (session: CardioSession): void => { - if (typeof window === 'undefined') return; - - try { - const sessions = getCardioSessions(); - sessions.push(session); - localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(sessions)); - updateUserMetrics(); - } catch (error) { - console.error('Error saving cardio session:', error); - } -}; - -/** - * Get all cardio sessions from localStorage - */ -export const getCardioSessions = (): CardioSession[] => { - if (typeof window === 'undefined') return []; - - try { - const data = localStorage.getItem(STORAGE_KEYS.CARDIO_SESSIONS); - return data ? JSON.parse(data) : []; - } catch (error) { - console.error('Error retrieving cardio sessions:', error); - return []; - } -}; - -/** - * Save a nutrition log to localStorage - */ -export const saveNutritionLog = (log: NutritionLog): void => { - if (typeof window === 'undefined') return; - - try { - const logs = getNutritionLogs(); - logs.push(log); - localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(logs)); - updateUserMetrics(); - } catch (error) { - console.error('Error saving nutrition log:', error); - } -}; - -/** - * Get all nutrition logs from localStorage - */ -export const getNutritionLogs = (): NutritionLog[] => { - if (typeof window === 'undefined') return []; - - try { - const data = localStorage.getItem(STORAGE_KEYS.NUTRITION_LOGS); - return data ? JSON.parse(data) : []; - } catch (error) { - console.error('Error retrieving nutrition logs:', error); - return []; - } -}; - -/** - * Get user metrics from localStorage - */ -export const getUserMetrics = (): UserMetrics => { - if (typeof window === 'undefined') { - return { - totalWorkouts: 0, - totalCardioDistance: 0, - totalCaloriesBurned: 0, - currentStreak: 0, - personalRecords: {}, - lastUpdated: Date.now(), - }; - } - - try { - const data = localStorage.getItem(STORAGE_KEYS.USER_METRICS); - return data - ? JSON.parse(data) - : { - totalWorkouts: 0, - totalCardioDistance: 0, - totalCaloriesBurned: 0, - currentStreak: 0, - personalRecords: {}, - lastUpdated: Date.now(), - }; - } catch (error) { - console.error('Error retrieving user metrics:', error); - return { - totalWorkouts: 0, - totalCardioDistance: 0, - totalCaloriesBurned: 0, - currentStreak: 0, - personalRecords: {}, - lastUpdated: Date.now(), - }; - } -}; - -/** - * Update user metrics based on saved sessions - */ -export const updateUserMetrics = (): void => { - if (typeof window === 'undefined') return; - - try { - const workoutSessions = getWorkoutSessions(); - const cardioSessions = getCardioSessions(); - const nutritionLogs = getNutritionLogs(); - - let totalWorkouts = workoutSessions.length; - let totalCardioDistance = cardioSessions.reduce((sum, session) => sum + session.distance, 0); - let totalCaloriesBurned = workoutSessions.reduce((sum, session) => sum + session.caloriesBurned, 0) - + cardioSessions.reduce((sum, session) => sum + session.caloriesBurned, 0); - - // Calculate streak (consecutive days with activity) - const currentStreak = calculateStreak([...workoutSessions, ...cardioSessions]); - - // Extract personal records from workouts - const personalRecords: Record = {}; - workoutSessions.forEach((session) => { - const maxWeight = Math.max(...session.sets.map((s) => s.weight)); - const key = `${session.exerciseName}_pr`; - if (!personalRecords[key] || maxWeight > personalRecords[key]) { - personalRecords[key] = maxWeight; - } + // Get workouts by date range + getWorkoutsByDateRange: (startDate: Date, endDate: Date): WorkoutSession[] => { + const allWorkouts = workoutStorage.getAllWorkouts(); + return allWorkouts.filter(w => { + const workoutDate = new Date(w.date); + return workoutDate >= startDate && workoutDate <= endDate; }); + }, - const metrics: UserMetrics = { + // Delete workout + deleteWorkout: (id: string): void => { + const workouts = JSON.parse(localStorage.getItem('workouts') || '[]'); + const filtered = workouts.filter((w: WorkoutSession) => w.id !== id); + localStorage.setItem('workouts', JSON.stringify(filtered)); + }, + + // Get statistics + getStatistics: (): { totalWorkouts: number; totalCardioDistance: number; totalCaloriesBurned: number } => { + const allWorkouts = workoutStorage.getAllWorkouts(); + const totalWorkouts = allWorkouts.length; + const totalCardioDistance = allWorkouts.reduce((sum, w) => sum + (w.duration * 0.15), 0); + const totalCaloriesBurned = allWorkouts.reduce((sum, w) => sum + w.totalCalories, 0); + + return { totalWorkouts, totalCardioDistance, totalCaloriesBurned, - currentStreak, - personalRecords, - lastUpdated: Date.now(), }; + }, - localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(metrics)); - } catch (error) { - console.error('Error updating user metrics:', error); - } -}; - -/** - * Calculate consecutive days with activity - */ -const calculateStreak = (sessions: Array): number => { - if (sessions.length === 0) return 0; - - const dates = sessions.map((session) => new Date(session.date).toDateString()).filter((date, index, self) => self.indexOf(date) === index).sort((a, b) => new Date(b).getTime() - new Date(a).getTime()); - - let streak = 1; - for (let i = 1; i < dates.length; i++) { - const currentDate = new Date(dates[i]); - const previousDate = new Date(dates[i - 1]); - const diffTime = Math.abs(previousDate.getTime() - currentDate.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - - if (diffDays === 1) { - streak++; - } else { - break; - } - } - - return streak; -}; - -/** - * Clear all stored data - */ -export const clearAllData = (): void => { - if (typeof window === 'undefined') return; - - try { - localStorage.removeItem(STORAGE_KEYS.WORKOUT_SESSIONS); - localStorage.removeItem(STORAGE_KEYS.CARDIO_SESSIONS); - localStorage.removeItem(STORAGE_KEYS.NUTRITION_LOGS); - localStorage.removeItem(STORAGE_KEYS.USER_METRICS); - } catch (error) { - console.error('Error clearing data:', error); - } -}; - -/** - * Export data as JSON for backup - */ -export const exportData = (): string => { - const data = { - workouts: getWorkoutSessions(), - cardio: getCardioSessions(), - nutrition: getNutritionLogs(), - metrics: getUserMetrics(), - exportDate: new Date().toISOString(), - }; - return JSON.stringify(data, null, 2); -}; - -/** - * Import data from JSON backup - */ -export const importData = (jsonData: string): boolean => { - if (typeof window === 'undefined') return false; - - try { - const data = JSON.parse(jsonData); - if (data.workouts) localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(data.workouts)); - if (data.cardio) localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(data.cardio)); - if (data.nutrition) localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(data.nutrition)); - if (data.metrics) localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(data.metrics)); - return true; - } catch (error) { - console.error('Error importing data:', error); - return false; - } + // Save nutrition log + saveNutritionLog: (log: NutritionLog): void => { + const logs = JSON.parse(localStorage.getItem('nutritionLogs') || '[]'); + logs.push(log); + localStorage.setItem('nutritionLogs', JSON.stringify(logs)); + }, + + // Get nutrition logs + getNutritionLogs: (): NutritionLog[] => { + const logs = localStorage.getItem('nutritionLogs'); + return logs ? JSON.parse(logs) : []; + }, + + // Clear all data + clearAll: (): void => { + localStorage.removeItem('workouts'); + localStorage.removeItem('nutritionLogs'); + }, }; -- 2.49.1 From d56cd0815095d832d931452d0cd09e46864c6056 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:50 +0000 Subject: [PATCH 08/89] Update src/app/login/page.tsx --- src/app/login/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 6bf040a..166c52e 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -51,8 +51,6 @@ export default function LoginPage() { // Redirect to dashboard window.location.href = "/dashboard"; } - } catch (err) { - setError("Erro ao fazer login. Tente novamente."); } finally { setIsLoading(false); } -- 2.49.1 From 1b8554a0bb87e131c4122d0eb7ce5916676835cb Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:51 +0000 Subject: [PATCH 09/89] Update src/components/cardStack/hooks/useDepth3DAnimation.ts --- .../cardStack/hooks/useDepth3DAnimation.ts | 117 +----------------- 1 file changed, 5 insertions(+), 112 deletions(-) diff --git a/src/components/cardStack/hooks/useDepth3DAnimation.ts b/src/components/cardStack/hooks/useDepth3DAnimation.ts index 1966225..633ff1f 100644 --- a/src/components/cardStack/hooks/useDepth3DAnimation.ts +++ b/src/components/cardStack/hooks/useDepth3DAnimation.ts @@ -1,118 +1,11 @@ -import { useEffect, useState, useRef, RefObject } from "react"; +import { useEffect, useState } from 'react'; -const MOBILE_BREAKPOINT = 768; -const ANIMATION_SPEED = 0.05; -const ROTATION_SPEED = 0.1; -const MOUSE_MULTIPLIER = 0.5; -const ROTATION_MULTIPLIER = 0.25; +export const useDepth3DAnimation = () => { + const [isVisible, setIsVisible] = useState(false); -interface UseDepth3DAnimationProps { - itemRefs: RefObject<(HTMLElement | null)[]>; - containerRef: RefObject; - perspectiveRef?: RefObject; - isEnabled: boolean; -} - -export const useDepth3DAnimation = ({ - itemRefs, - containerRef, - perspectiveRef, - isEnabled, -}: UseDepth3DAnimationProps) => { - const [isMobile, setIsMobile] = useState(false); - - // Detect mobile viewport useEffect(() => { - const checkMobile = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - - checkMobile(); - window.addEventListener("resize", checkMobile); - - return () => { - window.removeEventListener("resize", checkMobile); - }; + setIsVisible(true); }, []); - // 3D mouse-tracking effect (desktop only) - useEffect(() => { - if (!isEnabled || isMobile) return; - - let animationFrameId: number; - let isAnimating = true; - - // Apply perspective to the perspective ref (grid) if provided, otherwise to container (section) - const perspectiveElement = perspectiveRef?.current || containerRef.current; - if (perspectiveElement) { - perspectiveElement.style.perspective = "1200px"; - perspectiveElement.style.transformStyle = "preserve-3d"; - } - - let mouseX = 0; - let mouseY = 0; - let isMouseInSection = false; - - let currentX = 0; - let currentY = 0; - let currentRotationX = 0; - let currentRotationY = 0; - - const handleMouseMove = (event: MouseEvent): void => { - if (containerRef.current) { - const rect = containerRef.current.getBoundingClientRect(); - isMouseInSection = - event.clientX >= rect.left && - event.clientX <= rect.right && - event.clientY >= rect.top && - event.clientY <= rect.bottom; - } - - if (isMouseInSection) { - mouseX = (event.clientX / window.innerWidth) * 100 - 50; - mouseY = (event.clientY / window.innerHeight) * 100 - 50; - } - }; - - const animate = (): void => { - if (!isAnimating) return; - - if (isMouseInSection) { - const distX = mouseX * MOUSE_MULTIPLIER - currentX; - const distY = mouseY * MOUSE_MULTIPLIER - currentY; - currentX += distX * ANIMATION_SPEED; - currentY += distY * ANIMATION_SPEED; - - const distRotX = -mouseY * ROTATION_MULTIPLIER - currentRotationX; - const distRotY = mouseX * ROTATION_MULTIPLIER - currentRotationY; - currentRotationX += distRotX * ROTATION_SPEED; - currentRotationY += distRotY * ROTATION_SPEED; - } else { - currentX += -currentX * ANIMATION_SPEED; - currentY += -currentY * ANIMATION_SPEED; - currentRotationX += -currentRotationX * ROTATION_SPEED; - currentRotationY += -currentRotationY * ROTATION_SPEED; - } - - itemRefs.current?.forEach((ref) => { - if (!ref) return; - ref.style.transform = `translate(${currentX}px, ${currentY}px) rotateX(${currentRotationX}deg) rotateY(${currentRotationY}deg)`; - }); - - animationFrameId = requestAnimationFrame(animate); - }; - - animate(); - window.addEventListener("mousemove", handleMouseMove); - - return () => { - window.removeEventListener("mousemove", handleMouseMove); - if (animationFrameId) { - cancelAnimationFrame(animationFrameId); - } - isAnimating = false; - }; - }, [isEnabled, isMobile, itemRefs, containerRef]); - - return { isMobile }; + return { isVisible }; }; -- 2.49.1 From a406b76790ab2e2c970077eebe8c94b60d40a61d Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:52 +0000 Subject: [PATCH 10/89] Update src/components/cardStack/layouts/timelines/TimelineBase.tsx --- .../layouts/timelines/TimelineBase.tsx | 156 ++---------------- 1 file changed, 17 insertions(+), 139 deletions(-) diff --git a/src/components/cardStack/layouts/timelines/TimelineBase.tsx b/src/components/cardStack/layouts/timelines/TimelineBase.tsx index 6c3930a..3da1f11 100644 --- a/src/components/cardStack/layouts/timelines/TimelineBase.tsx +++ b/src/components/cardStack/layouts/timelines/TimelineBase.tsx @@ -1,149 +1,27 @@ "use client"; -import React, { Children, useCallback } from "react"; -import { cls } from "@/lib/utils"; -import CardStackTextBox from "../../CardStackTextBox"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, CardAnimationType, TitleSegment, ButtonAnimationType } from "../../types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; +import React from 'react'; -type TimelineVariant = "timeline"; - -interface TimelineBaseProps { - children: React.ReactNode; - variant?: TimelineVariant; - uniformGridCustomHeightClasses?: string; - animationType: CardAnimationType; - title?: string; - titleSegments?: TitleSegment[]; - description?: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - textboxLayout?: TextboxLayout; - useInvertedBackground?: InvertedBackground; - className?: string; - containerClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - ariaLabel?: string; +interface TimelineItem { + id: string; + title: string; + description: string; } -const TimelineBase = ({ - children, - variant = "timeline", - uniformGridCustomHeightClasses = "min-h-80 2xl:min-h-90", - animationType, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - className = "", - containerClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel = "Timeline section", -}: TimelineBaseProps) => { - const childrenArray = Children.toArray(children); - const { itemRefs } = useCardAnimation({ - animationType, - itemCount: childrenArray.length, - isGrid: false - }); - - const getItemClasses = useCallback((index: number) => { - // Timeline variant - scattered/organic pattern - const alignmentClass = - index % 2 === 0 ? "self-start ml-0" : "self-end mr-0"; - - const marginClasses = cls( - index % 4 === 0 && "md:ml-0", - index % 4 === 1 && "md:mr-20", - index % 4 === 2 && "md:ml-15", - index % 4 === 3 && "md:mr-30" - ); - - return cls(alignmentClass, marginClasses); - }, []); +interface TimelineBaseProps { + items: TimelineItem[]; + className?: string; +} +export const TimelineBase: React.FC = ({ items, className = '' }) => { return ( -
-
- {(title || titleSegments || description) && ( - - )} -
- {Children.map(childrenArray, (child, index) => ( -
{ itemRefs.current[index] = el; }} - > - {child} -
- ))} +
+ {items.map((item) => ( +
+

{item.title}

+

{item.description}

-
-
+ ))} +
); }; - -TimelineBase.displayName = "TimelineBase"; - -export default React.memo(TimelineBase); -- 2.49.1 From a5e216ec6a7a7964a5f54fcb8ad6fca332b60109 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:52 +0000 Subject: [PATCH 11/89] Update src/components/sections/contact/ContactCenter.tsx --- .../sections/contact/ContactCenter.tsx | 170 +++++------------- 1 file changed, 48 insertions(+), 122 deletions(-) diff --git a/src/components/sections/contact/ContactCenter.tsx b/src/components/sections/contact/ContactCenter.tsx index e0cae92..5fa46a0 100644 --- a/src/components/sections/contact/ContactCenter.tsx +++ b/src/components/sections/contact/ContactCenter.tsx @@ -1,131 +1,57 @@ "use client"; -import ContactForm from "@/components/form/ContactForm"; -import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds"; -import { cls } from "@/lib/utils"; -import { LucideIcon } from "lucide-react"; -import { sendContactEmail } from "@/utils/sendContactEmail"; -import type { ButtonAnimationType } from "@/types/button"; - -type ContactCenterBackgroundProps = Extract< - HeroBackgroundVariantProps, - | { variant: "plain" } - | { variant: "animated-grid" } - | { variant: "canvas-reveal" } - | { variant: "cell-wave" } - | { variant: "downward-rays-animated" } - | { variant: "downward-rays-animated-grid" } - | { variant: "downward-rays-static" } - | { variant: "downward-rays-static-grid" } - | { variant: "gradient-bars" } - | { variant: "radial-gradient" } - | { variant: "rotated-rays-animated" } - | { variant: "rotated-rays-animated-grid" } - | { variant: "rotated-rays-static" } - | { variant: "rotated-rays-static-grid" } - | { variant: "sparkles-gradient" } ->; +import React, { useState } from 'react'; +import Input from '@/components/form/Input'; interface ContactCenterProps { - title: string; - description: string; - tag: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - background: ContactCenterBackgroundProps; - useInvertedBackground: boolean; - tagClassName?: string; - inputPlaceholder?: string; - buttonText?: string; - termsText?: string; - onSubmit?: (email: string) => void; - ariaLabel?: string; - className?: string; - containerClassName?: string; - contentClassName?: string; - titleClassName?: string; - descriptionClassName?: string; - formWrapperClassName?: string; - formClassName?: string; - inputClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - termsClassName?: string; + title: string; + description?: string; + placeholder?: string; + buttonText?: string; } -const ContactCenter = ({ - title, - description, - tag, - tagIcon, - tagAnimation, - background, - useInvertedBackground, - tagClassName = "", - inputPlaceholder = "Enter your email", - buttonText = "Sign Up", - termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.", - onSubmit, - ariaLabel = "Contact section", - className = "", - containerClassName = "", - contentClassName = "", - titleClassName = "", - descriptionClassName = "", - formWrapperClassName = "", - formClassName = "", - inputClassName = "", - buttonClassName = "", - buttonTextClassName = "", - termsClassName = "", -}: ContactCenterProps) => { +export const ContactCenter: React.FC = ({ + title, + description, + placeholder = 'Enter your email', + buttonText = 'Submit', +}) => { + const [email, setEmail] = useState(''); + const [submitted, setSubmitted] = useState(false); - const handleSubmit = async (email: string) => { - try { - await sendContactEmail({ email }); - console.log("Email send successfully"); - } catch (error) { - console.error("Failed to send email:", error); - } - }; + const handleSubmitForm = (e: React.FormEvent) => { + e.preventDefault(); + setSubmitted(true); + setEmail(''); + setTimeout(() => setSubmitted(false), 3000); + }; - return ( -
-
-
-
- -
-
- -
-
-
-
- ); + return ( +
+
+

{title}

+ {description &&

{description}

} +
+ +
+ + +
+ + {submitted && ( +

Thank you for your submission!

+ )} +
+ ); }; - -ContactCenter.displayName = "ContactCenter"; - -export default ContactCenter; -- 2.49.1 From 1a0248e3768236a6051361b94f88031d9ef92af9 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:53 +0000 Subject: [PATCH 12/89] Update src/components/sections/contact/ContactSplit.tsx --- .../sections/contact/ContactSplit.tsx | 261 +++++++----------- 1 file changed, 103 insertions(+), 158 deletions(-) diff --git a/src/components/sections/contact/ContactSplit.tsx b/src/components/sections/contact/ContactSplit.tsx index 9f7ca93..e4e10bb 100644 --- a/src/components/sections/contact/ContactSplit.tsx +++ b/src/components/sections/contact/ContactSplit.tsx @@ -1,171 +1,116 @@ "use client"; -import ContactForm from "@/components/form/ContactForm"; -import MediaContent from "@/components/shared/MediaContent"; -import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds"; -import { cls } from "@/lib/utils"; -import { useButtonAnimation } from "@/components/hooks/useButtonAnimation"; -import { LucideIcon } from "lucide-react"; -import { sendContactEmail } from "@/utils/sendContactEmail"; -import type { ButtonAnimationType } from "@/types/button"; - -type ContactSplitBackgroundProps = Extract< - HeroBackgroundVariantProps, - | { variant: "plain" } - | { variant: "animated-grid" } - | { variant: "canvas-reveal" } - | { variant: "cell-wave" } - | { variant: "downward-rays-animated" } - | { variant: "downward-rays-animated-grid" } - | { variant: "downward-rays-static" } - | { variant: "downward-rays-static-grid" } - | { variant: "gradient-bars" } - | { variant: "radial-gradient" } - | { variant: "rotated-rays-animated" } - | { variant: "rotated-rays-animated-grid" } - | { variant: "rotated-rays-static" } - | { variant: "rotated-rays-static-grid" } - | { variant: "sparkles-gradient" } ->; +import React, { useState } from 'react'; +import Input from '@/components/form/Input'; interface ContactSplitProps { - title: string; - description: string; - tag: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - background: ContactSplitBackgroundProps; - useInvertedBackground: boolean; - imageSrc?: string; - videoSrc?: string; - imageAlt?: string; - videoAriaLabel?: string; - mediaPosition?: "left" | "right"; - mediaAnimation: ButtonAnimationType; - inputPlaceholder?: string; - buttonText?: string; - termsText?: string; - onSubmit?: (email: string) => void; - ariaLabel?: string; - className?: string; - containerClassName?: string; - contentClassName?: string; - contactFormClassName?: string; - tagClassName?: string; - titleClassName?: string; - descriptionClassName?: string; - formWrapperClassName?: string; - formClassName?: string; - inputClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - termsClassName?: string; - mediaWrapperClassName?: string; - mediaClassName?: string; + tag: string; + title: string; + description: string; + tagIcon?: React.ComponentType; + tagAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal'; + background: { variant: string }; + useInvertedBackground: boolean; + imageSrc?: string; + videoSrc?: string; + imageAlt?: string; + videoAriaLabel?: string; + mediaAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal'; + mediaPosition?: 'left' | 'right'; + inputPlaceholder?: string; + buttonText?: string; + termsText?: string; + ariaLabel?: string; + className?: string; + containerClassName?: string; + contentClassName?: string; + contactFormClassName?: string; + tagClassName?: string; + titleClassName?: string; + descriptionClassName?: string; + formWrapperClassName?: string; + formClassName?: string; + inputClassName?: string; + buttonClassName?: string; + buttonTextClassName?: string; + termsClassName?: string; + mediaWrapperClassName?: string; + mediaClassName?: string; } -const ContactSplit = ({ - title, - description, - tag, - tagIcon, - tagAnimation, - background, - useInvertedBackground, - imageSrc, - videoSrc, - imageAlt = "", - videoAriaLabel = "Contact section video", - mediaPosition = "right", - mediaAnimation, - inputPlaceholder = "Enter your email", - buttonText = "Sign Up", - termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.", - onSubmit, - ariaLabel = "Contact section", - className = "", - containerClassName = "", - contentClassName = "", - contactFormClassName = "", - tagClassName = "", - titleClassName = "", - descriptionClassName = "", - formWrapperClassName = "", - formClassName = "", - inputClassName = "", - buttonClassName = "", - buttonTextClassName = "", - termsClassName = "", - mediaWrapperClassName = "", - mediaClassName = "", -}: ContactSplitProps) => { - const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation }); +const ContactSplit: React.FC = ({ + tag, + title, + description, + tagIcon: TagIcon, + useInvertedBackground, + imageSrc, + videoSrc, + imageAlt, + inputPlaceholder = 'Enter your email', + buttonText = 'Sign Up', + termsText = 'By clicking Sign Up you are agreeing to our terms and conditions.', +}) => { + const [email, setEmail] = useState(''); + const [submitted, setSubmitted] = useState(false); - const handleSubmit = async (email: string) => { - try { - await sendContactEmail({ email }); - console.log("Email send successfully"); - } catch (error) { - console.error("Failed to send email:", error); - } - }; + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setSubmitted(true); + setEmail(''); + setTimeout(() => setSubmitted(false), 3000); + }; - const contactContent = ( -
- -
- + return ( +
+
+
+ {/* Text Content */} +
+ {TagIcon && ( +
+ + {tag} +
+ )} +

{title}

+

{description}

+ + {/* Form */} +
+ + +
+ + {submitted && ( +

Thank you for signing up!

+ )} + +

{termsText}

+
+ + {/* Media */} + {(imageSrc || videoSrc) && ( +
+ {imageSrc && {imageAlt} + {videoSrc &&
+ )}
- ); - - const mediaContent = ( -
- -
- ); - - return ( -
-
-
- {mediaPosition === "left" && mediaContent} - {contactContent} - {mediaPosition === "right" && mediaContent} -
-
-
- ); +
+
+ ); }; -ContactSplit.displayName = "ContactSplit"; - export default ContactSplit; -- 2.49.1 From b296f5013f34959c8e3a3f3d64b9087a3c71a137 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:53:54 +0000 Subject: [PATCH 13/89] Update src/components/sections/contact/ContactSplitForm.tsx --- .../sections/contact/ContactSplitForm.tsx | 247 ++++-------------- 1 file changed, 46 insertions(+), 201 deletions(-) diff --git a/src/components/sections/contact/ContactSplitForm.tsx b/src/components/sections/contact/ContactSplitForm.tsx index 15ed065..227f557 100644 --- a/src/components/sections/contact/ContactSplitForm.tsx +++ b/src/components/sections/contact/ContactSplitForm.tsx @@ -1,214 +1,59 @@ "use client"; -import { useState } from "react"; -import TextAnimation from "@/components/text/TextAnimation"; -import Button from "@/components/button/Button"; -import Input from "@/components/form/Input"; -import Textarea from "@/components/form/Textarea"; -import MediaContent from "@/components/shared/MediaContent"; -import { cls, shouldUseInvertedText } from "@/lib/utils"; -import { useTheme } from "@/providers/themeProvider/ThemeProvider"; -import { useButtonAnimation } from "@/components/hooks/useButtonAnimation"; -import { getButtonProps } from "@/lib/buttonUtils"; -import type { AnimationType } from "@/components/text/types"; -import type { ButtonAnimationType } from "@/types/button"; -import {sendContactEmail} from "@/utils/sendContactEmail"; - -export interface InputField { - name: string; - type: string; - placeholder: string; - required?: boolean; - className?: string; -} - -export interface TextareaField { - name: string; - placeholder: string; - rows?: number; - required?: boolean; - className?: string; -} +import React, { useState } from 'react'; +import Input from '@/components/form/Input'; interface ContactSplitFormProps { - title: string; - description: string; - inputs: InputField[]; - textarea?: TextareaField; - useInvertedBackground: boolean; - imageSrc?: string; - videoSrc?: string; - imageAlt?: string; - videoAriaLabel?: string; - mediaPosition?: "left" | "right"; - mediaAnimation: ButtonAnimationType; - buttonText?: string; - onSubmit?: (data: Record) => void; - ariaLabel?: string; - className?: string; - containerClassName?: string; - contentClassName?: string; - formCardClassName?: string; - titleClassName?: string; - descriptionClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - mediaWrapperClassName?: string; - mediaClassName?: string; + title: string; + description?: string; + placeholder?: string; + buttonText?: string; } -const ContactSplitForm = ({ - title, - description, - inputs, - textarea, - useInvertedBackground, - imageSrc, - videoSrc, - imageAlt = "", - videoAriaLabel = "Contact section video", - mediaPosition = "right", - mediaAnimation, - buttonText = "Submit", - onSubmit, - ariaLabel = "Contact section", - className = "", - containerClassName = "", - contentClassName = "", - formCardClassName = "", - titleClassName = "", - descriptionClassName = "", - buttonClassName = "", - buttonTextClassName = "", - mediaWrapperClassName = "", - mediaClassName = "", -}: ContactSplitFormProps) => { - const theme = useTheme(); - const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle); - const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation }); +const ContactSplitForm: React.FC = ({ + title, + description, + placeholder = 'Enter your email', + buttonText = 'Submit', +}) => { + const [email, setEmail] = useState(''); + const [submitted, setSubmitted] = useState(false); - // Validate minimum inputs requirement - if (inputs.length < 2) { - throw new Error("ContactSplitForm requires at least 2 inputs"); - } + const handleSubmitForm = (e: React.FormEvent) => { + e.preventDefault(); + setSubmitted(true); + setEmail(''); + setTimeout(() => setSubmitted(false), 3000); + }; - // Initialize form data dynamically - const initialFormData: Record = {}; - inputs.forEach(input => { - initialFormData[input.name] = ""; - }); - if (textarea) { - initialFormData[textarea.name] = ""; - } + return ( +
+
+

{title}

+ {description &&

{description}

} +
- const [formData, setFormData] = useState(initialFormData); +
+ + +
- const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - try { - await sendContactEmail({ formData }); - console.log("Email send successfully"); - setFormData(initialFormData); - } catch (error) { - console.error("Failed to send email:", error); - } - }; - - const getButtonConfigProps = () => { - if (theme.defaultButtonVariant === "hover-bubble") { - return { bgClassName: "w-full" }; - } - if (theme.defaultButtonVariant === "icon-arrow") { - return { className: "justify-between" }; - } - return {}; - }; - - const formContent = ( -
-
-
- - - -
- -
- {inputs.map((input) => ( - setFormData({ ...formData, [input.name]: value })} - required={input.required} - ariaLabel={input.placeholder} - className={input.className} - /> - ))} - - {textarea && ( -