Enhance UI components across the application by integrating theme support for dark mode. Update styles in Login, Book Now, User Dashboard, and Settings pages to improve visual consistency. Refactor various components to utilize the ThemeProvider for dynamic styling based on the selected theme.
This commit is contained in:
parent
1cf94cca0a
commit
c0ff0386a0
@ -7,8 +7,11 @@ import { Heart, Eye, EyeOff, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export default function Login() {
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const router = useRouter();
|
||||
@ -38,29 +41,33 @@ export default function Login() {
|
||||
|
||||
|
||||
{/* Centered White Card - Login Form */}
|
||||
<div className="relative z-20 w-full max-w-md bg-white rounded-2xl shadow-2xl p-8">
|
||||
<div className={`relative z-20 w-full max-w-md rounded-2xl shadow-2xl p-8 ${isDark ? 'bg-gray-800 border border-gray-700' : 'bg-white'}`}>
|
||||
{/* Header with Close Button */}
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex-1">
|
||||
{/* Heading */}
|
||||
<h1 className="text-3xl font-bold bg-gradient-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent mb-2">
|
||||
Welcome back
|
||||
</h1>
|
||||
{/* Sign Up Prompt */}
|
||||
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
New to Attune Heart Therapy?{" "}
|
||||
<Link href="/signup" className={`underline font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}>
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
{/* Close Button */}
|
||||
<Button
|
||||
onClick={() => router.back()}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-auto mb-6 w-8 h-8 rounded-full"
|
||||
className={`flex-shrink-0 w-8 h-8 rounded-full ${isDark ? 'text-gray-400 hover:text-gray-300 hover:bg-gray-700' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'}`}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
|
||||
{/* Heading */}
|
||||
<h1 className="text-3xl font-bold bg-linear-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent mb-2">
|
||||
Welcome back
|
||||
</h1>
|
||||
|
||||
{/* Sign Up Prompt */}
|
||||
<p className="text-gray-600 mb-8">
|
||||
New to Attune Heart Therapy?{" "}
|
||||
<Link href="/signup" className="text-blue-600 underline font-medium">
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Login Form */}
|
||||
<form className="space-y-6" onSubmit={(e) => {
|
||||
@ -69,21 +76,21 @@ export default function Login() {
|
||||
}}>
|
||||
{/* Email Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="text-sm font-medium text-black">
|
||||
<label htmlFor="email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Email address
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
className="h-12 bg-white border-gray-300"
|
||||
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="password" className="text-sm font-medium text-black">
|
||||
<label htmlFor="password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Your password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@ -91,7 +98,7 @@ export default function Login() {
|
||||
id="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Your password"
|
||||
className="h-12 bg-white border-gray-300 pr-12"
|
||||
className={`h-12 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
@ -99,7 +106,7 @@ export default function Login() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 text-gray-500 hover:text-gray-700"
|
||||
className={`absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`}
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
>
|
||||
{showPassword ? (
|
||||
@ -114,7 +121,7 @@ export default function Login() {
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full h-12 text-base font-semibold bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all"
|
||||
className="w-full h-12 text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all"
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
@ -126,13 +133,13 @@ export default function Login() {
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300 text-rose-600 focus:ring-2 focus:ring-rose-500 cursor-pointer"
|
||||
className={`w-4 h-4 rounded text-rose-600 focus:ring-2 focus:ring-rose-500 cursor-pointer ${isDark ? 'border-gray-600 bg-gray-700' : 'border-gray-300'}`}
|
||||
/>
|
||||
<span className="text-black">Remember me</span>
|
||||
<span className={isDark ? 'text-gray-300' : 'text-black'}>Remember me</span>
|
||||
</label>
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="text-blue-600 hover:text-blue-700 font-medium"
|
||||
className={`font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
@ -70,6 +71,8 @@ interface BookingsResponse {
|
||||
|
||||
export default function BookNowPage() {
|
||||
const router = useRouter();
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
@ -208,11 +211,11 @@ export default function BookNowPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className={`min-h-screen ${isDark ? 'bg-gray-900' : 'bg-white'}`}>
|
||||
{/* Main Content */}
|
||||
<main className="min-h-screen flex">
|
||||
{/* Left Side - Image (Fixed) */}
|
||||
<div className="hidden lg:block fixed top-0 left-0 h-screen w-1/2 overflow-hidden z-10 bg-gradient-to-br from-rose-100 via-pink-50 to-orange-50">
|
||||
<div className={`hidden lg:block fixed top-0 left-0 h-screen w-1/2 overflow-hidden z-10 bg-gradient-to-br ${isDark ? 'from-gray-900 via-gray-800 to-gray-900' : 'from-rose-100 via-pink-50 to-orange-50'}`}>
|
||||
<div className="absolute inset-0">
|
||||
<Image
|
||||
src="/doctors.png"
|
||||
@ -231,7 +234,7 @@ export default function BookNowPage() {
|
||||
<div className="bg-gradient-to-r from-rose-500 to-pink-600 p-2 rounded-xl">
|
||||
<Heart className="h-5 w-5 text-white fill-white" />
|
||||
</div>
|
||||
<span className="font-bold text-lg text-rose-500 drop-shadow-lg">
|
||||
<span className={`font-bold text-lg drop-shadow-lg ${isDark ? 'text-rose-400' : 'text-rose-500'}`}>
|
||||
Attune Heart Therapy
|
||||
</span>
|
||||
</Link>
|
||||
@ -279,78 +282,78 @@ export default function BookNowPage() {
|
||||
</div>
|
||||
|
||||
{/* Right Side - Form (Scrollable) */}
|
||||
<div className="w-full lg:w-1/2 lg:ml-auto fixed top-0 right-0 h-screen overflow-y-auto bg-white custom-scrollbar">
|
||||
<div className={`w-full lg:w-1/2 lg:ml-auto fixed top-0 right-0 h-screen overflow-y-auto custom-scrollbar ${isDark ? 'bg-gray-900' : 'bg-white'}`}>
|
||||
<div className="flex items-start justify-center min-h-full">
|
||||
<div className="w-full max-w-2xl">
|
||||
{/* Page Header */}
|
||||
<div className="pt-4 sm:pt-6 lg:pt-8 px-6 sm:px-8 lg:px-12 pb-6">
|
||||
<div className="pt-4 sm:pt-6 lg:pt-8 px-4 sm:px-6 lg:px-12 pb-4 sm:pb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => router.back()}
|
||||
className="flex items-center gap-2 text-black hover:bg-gray-100 mb-4"
|
||||
className={`flex items-center gap-2 mb-3 sm:mb-4 ${isDark ? 'text-white hover:bg-gray-800' : 'text-black hover:bg-gray-100'}`}
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">Back</span>
|
||||
<ArrowLeft className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<span className="hidden sm:inline text-sm sm:text-base">Back</span>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900 mb-1">
|
||||
<h1 className={`text-xl sm:text-2xl font-semibold mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Book Your Appointment
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className={`text-xs sm:text-sm ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Fill out the form below and we'll get back to you to confirm your appointment
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Booking Form or Success Message */}
|
||||
<div className="px-6 sm:px-8 lg:px-12 pb-6 sm:pb-8 lg:pb-12">
|
||||
<div className="px-4 sm:px-6 lg:px-12 pb-6 sm:pb-8 lg:pb-12">
|
||||
{booking ? (
|
||||
<div className="bg-white rounded-2xl shadow-lg p-6 sm:p-8 border border-gray-200">
|
||||
<div className={`rounded-xl sm:rounded-2xl shadow-lg p-4 sm:p-6 lg:p-8 border ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}>
|
||||
<div className="text-center space-y-4">
|
||||
<div className="mx-auto w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||
<div className={`mx-auto w-16 h-16 rounded-full flex items-center justify-center ${isDark ? 'bg-green-900/30' : 'bg-green-100'}`}>
|
||||
<CheckCircle className={`w-8 h-8 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
||||
<h2 className={`text-2xl font-semibold mb-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Booking Confirmed!
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
<p className={isDark ? 'text-gray-300' : 'text-gray-600'}>
|
||||
Your appointment has been successfully booked.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-6 space-y-4 text-left">
|
||||
<div className={`rounded-lg p-6 space-y-4 text-left ${isDark ? 'bg-gray-700/50' : 'bg-gray-50'}`}>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Booking ID</p>
|
||||
<p className="text-base font-semibold text-gray-900">#{booking.ID}</p>
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Booking ID</p>
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>#{booking.ID}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Patient</p>
|
||||
<p className="text-base text-gray-900">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Patient</p>
|
||||
<p className={`text-base ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
{booking.user.first_name} {booking.user.last_name}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Scheduled Time</p>
|
||||
<p className="text-base text-gray-900">{formatDateTime(booking.scheduled_at)}</p>
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Scheduled Time</p>
|
||||
<p className={`text-base ${isDark ? 'text-white' : 'text-gray-900'}`}>{formatDateTime(booking.scheduled_at)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Duration</p>
|
||||
<p className="text-base text-gray-900">{booking.duration} minutes</p>
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Duration</p>
|
||||
<p className={`text-base ${isDark ? 'text-white' : 'text-gray-900'}`}>{booking.duration} minutes</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Status</p>
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Status</p>
|
||||
<span className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${isDark ? 'bg-blue-900/50 text-blue-200' : 'bg-blue-100 text-blue-800'}`}>
|
||||
{booking.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Amount</p>
|
||||
<p className="text-base font-semibold text-gray-900">${booking.amount}</p>
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Amount</p>
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>${booking.amount}</p>
|
||||
</div>
|
||||
{booking.notes && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-1">Notes</p>
|
||||
<p className="text-base text-gray-900">{booking.notes}</p>
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>Notes</p>
|
||||
<p className={`text-base ${isDark ? 'text-white' : 'text-gray-900'}`}>{booking.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -384,17 +387,17 @@ export default function BookNowPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-white rounded-2xl shadow-lg p-6 sm:p-8 border border-gray-200">
|
||||
<div className={`rounded-xl sm:rounded-2xl shadow-lg p-4 sm:p-6 lg:p-8 border ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}>
|
||||
{error && (
|
||||
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
<div className={`mb-6 p-4 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}>
|
||||
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Personal Information Section */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<User className="w-5 h-5 text-rose-600" />
|
||||
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
<User className={`w-5 h-5 ${isDark ? 'text-rose-400' : 'text-rose-600'}`} />
|
||||
Personal Information
|
||||
</h2>
|
||||
|
||||
@ -402,7 +405,7 @@ export default function BookNowPage() {
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="firstName"
|
||||
className="text-sm font-medium text-gray-700"
|
||||
className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
First Name *
|
||||
</label>
|
||||
@ -415,14 +418,14 @@ export default function BookNowPage() {
|
||||
handleChange("firstName", e.target.value)
|
||||
}
|
||||
required
|
||||
className="h-11"
|
||||
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="lastName"
|
||||
className="text-sm font-medium text-gray-700"
|
||||
className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Last Name *
|
||||
</label>
|
||||
@ -435,7 +438,7 @@ export default function BookNowPage() {
|
||||
handleChange("lastName", e.target.value)
|
||||
}
|
||||
required
|
||||
className="h-11"
|
||||
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -443,9 +446,9 @@ export default function BookNowPage() {
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-sm font-medium text-gray-700 flex items-center gap-2"
|
||||
className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
<Mail className="w-4 h-4 text-gray-500" />
|
||||
<Mail className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`} />
|
||||
Email Address *
|
||||
</label>
|
||||
<Input
|
||||
@ -455,16 +458,16 @@ export default function BookNowPage() {
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange("email", e.target.value)}
|
||||
required
|
||||
className="h-11"
|
||||
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="text-sm font-medium text-gray-700 flex items-center gap-2"
|
||||
className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
<Phone className="w-4 h-4 text-gray-500" />
|
||||
<Phone className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`} />
|
||||
Phone Number *
|
||||
</label>
|
||||
<Input
|
||||
@ -474,22 +477,22 @@ export default function BookNowPage() {
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleChange("phone", e.target.value)}
|
||||
required
|
||||
className="h-11"
|
||||
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Appointment Details Section */}
|
||||
<div className="space-y-4 pt-6 border-t border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-rose-600" />
|
||||
<div className={`space-y-4 pt-6 border-t ${isDark ? 'border-gray-700' : 'border-gray-200'}`}>
|
||||
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
<Calendar className={`w-5 h-5 ${isDark ? 'text-rose-400' : 'text-rose-600'}`} />
|
||||
Appointment Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="appointmentType"
|
||||
className="text-sm font-medium text-gray-700"
|
||||
className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Appointment Type *
|
||||
</label>
|
||||
@ -500,10 +503,10 @@ export default function BookNowPage() {
|
||||
}
|
||||
required
|
||||
>
|
||||
<SelectTrigger id="appointmentType" className="h-11 bg-white">
|
||||
<SelectTrigger id="appointmentType" className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white' : 'bg-white border-gray-300 text-gray-900'}`}>
|
||||
<SelectValue placeholder="Select appointment type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectContent className={isDark ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-300'}>
|
||||
<SelectItem value="initial-consultation">
|
||||
Initial Consultation
|
||||
</SelectItem>
|
||||
@ -524,9 +527,9 @@ export default function BookNowPage() {
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="preferredDate"
|
||||
className="text-sm font-medium text-gray-700 flex items-center gap-2"
|
||||
className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
<Calendar className="w-4 h-4 text-gray-500" />
|
||||
<Calendar className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`} />
|
||||
Preferred Date *
|
||||
</label>
|
||||
<Input
|
||||
@ -538,16 +541,16 @@ export default function BookNowPage() {
|
||||
}
|
||||
required
|
||||
min={new Date().toISOString().split("T")[0]}
|
||||
className="h-11"
|
||||
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="preferredTime"
|
||||
className="text-sm font-medium text-gray-700 flex items-center gap-2"
|
||||
className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
<Clock className="w-4 h-4 text-gray-500" />
|
||||
<Clock className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`} />
|
||||
Preferred Time *
|
||||
</label>
|
||||
<Select
|
||||
@ -557,10 +560,10 @@ export default function BookNowPage() {
|
||||
}
|
||||
required
|
||||
>
|
||||
<SelectTrigger id="preferredTime" className="h-11">
|
||||
<SelectTrigger id="preferredTime" className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white' : 'bg-white border-gray-300 text-gray-900'}`}>
|
||||
<SelectValue placeholder="Select time" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectContent className={isDark ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-300'}>
|
||||
<SelectItem value="9:00 AM">9:00 AM</SelectItem>
|
||||
<SelectItem value="10:00 AM">10:00 AM</SelectItem>
|
||||
<SelectItem value="11:00 AM">11:00 AM</SelectItem>
|
||||
@ -577,12 +580,12 @@ export default function BookNowPage() {
|
||||
</div>
|
||||
|
||||
{/* Additional Message Section */}
|
||||
<div className="space-y-4 pt-6 border-t border-gray-200">
|
||||
<div className={`space-y-4 pt-6 border-t ${isDark ? 'border-gray-700' : 'border-gray-200'}`}>
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="text-sm font-medium text-gray-700 flex items-center gap-2"
|
||||
className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4 text-gray-500" />
|
||||
<MessageSquare className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`} />
|
||||
Additional Message (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
@ -591,7 +594,7 @@ export default function BookNowPage() {
|
||||
placeholder="Tell us about any specific concerns or preferences..."
|
||||
value={formData.message}
|
||||
onChange={(e) => handleChange("message", e.target.value)}
|
||||
className="w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-rose-500 focus-visible:border-rose-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className={`w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-rose-500 focus-visible:border-rose-500 disabled:cursor-not-allowed disabled:opacity-50 ${isDark ? 'border-gray-600 bg-gray-700 text-white placeholder:text-gray-400 focus-visible:ring-rose-400 focus-visible:border-rose-400' : 'border-gray-300 bg-white text-gray-900 placeholder:text-gray-500'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -612,7 +615,7 @@ export default function BookNowPage() {
|
||||
"Submit Booking Request"
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-xs text-gray-500 text-center mt-4">
|
||||
<p className={`text-xs text-center mt-4 ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
We'll review your request and get back to you within 24 hours
|
||||
to confirm your appointment.
|
||||
</p>
|
||||
@ -622,11 +625,11 @@ export default function BookNowPage() {
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-gray-600">
|
||||
<p className={isDark ? 'text-gray-300' : 'text-gray-600'}>
|
||||
Prefer to book by phone?{" "}
|
||||
<a
|
||||
href="tel:+19548073027"
|
||||
className="text-rose-600 hover:text-rose-700 font-medium underline"
|
||||
className={`font-medium underline ${isDark ? 'text-rose-400 hover:text-rose-300' : 'text-rose-600 hover:text-rose-700'}`}
|
||||
>
|
||||
Call us at (954) 807-3027
|
||||
</a>
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
interface Booking {
|
||||
ID: number;
|
||||
@ -31,6 +32,8 @@ interface Booking {
|
||||
}
|
||||
|
||||
export default function UserDashboard() {
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [bookings, setBookings] = useState<Booking[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@ -125,7 +128,7 @@ export default function UserDashboard() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? 'bg-gray-900' : 'bg-gray-50'}`}>
|
||||
<Navbar />
|
||||
|
||||
{/* Main Content */}
|
||||
@ -133,10 +136,10 @@ export default function UserDashboard() {
|
||||
{/* Welcome Section */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900 mb-1">
|
||||
<h1 className={`text-2xl font-semibold mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Welcome Back!
|
||||
</h1>
|
||||
<p className="text-sm text-gray-600">
|
||||
<p className={`text-sm ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Here's an overview of your appointments
|
||||
</p>
|
||||
</div>
|
||||
@ -144,7 +147,7 @@ export default function UserDashboard() {
|
||||
<Link href="/user/settings">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-gray-100"
|
||||
className={isDark ? 'hover:bg-gray-800 border-gray-700 text-gray-300' : 'hover:bg-gray-100'}
|
||||
>
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Settings
|
||||
@ -152,7 +155,7 @@ export default function UserDashboard() {
|
||||
</Link>
|
||||
<Link href="/book-now">
|
||||
<Button
|
||||
className="bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
className="bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
>
|
||||
<CalendarPlus className="w-4 h-4 mr-2" />
|
||||
Book Appointment
|
||||
@ -163,7 +166,7 @@ export default function UserDashboard() {
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-400"></div>
|
||||
<div className={`animate-spin rounded-full h-8 w-8 border-b-2 ${isDark ? 'border-gray-600' : 'border-gray-400'}`}></div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@ -174,17 +177,17 @@ export default function UserDashboard() {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-lg border border-gray-200 p-4 sm:p-5 md:p-6 hover:shadow-md transition-shadow"
|
||||
className={`rounded-lg border p-4 sm:p-5 md:p-6 hover:shadow-md transition-shadow ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3 sm:mb-4">
|
||||
<div className="p-2 sm:p-2.5 rounded-lg bg-gray-50">
|
||||
<Icon className="w-4 h-4 sm:w-5 sm:h-5 text-gray-600" />
|
||||
<div className={`p-2 sm:p-2.5 rounded-lg ${isDark ? 'bg-gray-700' : 'bg-gray-50'}`}>
|
||||
<Icon className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium ${
|
||||
card.trendUp
|
||||
? "bg-green-50 text-green-700"
|
||||
: "bg-red-50 text-red-700"
|
||||
? isDark ? "bg-green-900/30 text-green-400" : "bg-green-50 text-green-700"
|
||||
: isDark ? "bg-red-900/30 text-red-400" : "bg-red-50 text-red-700"
|
||||
}`}
|
||||
>
|
||||
{card.trendUp ? (
|
||||
@ -197,13 +200,13 @@ export default function UserDashboard() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs font-medium text-rose-600 mb-1 sm:mb-2 uppercase tracking-wider">
|
||||
<h3 className={`text-xs font-medium mb-1 sm:mb-2 uppercase tracking-wider ${isDark ? 'text-rose-400' : 'text-rose-600'}`}>
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="text-xl sm:text-2xl font-bold text-gray-900 mb-1">
|
||||
<p className={`text-xl sm:text-2xl font-bold mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
{card.value}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 font-medium">vs last month</p>
|
||||
<p className={`text-xs font-medium ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>vs last month</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -212,46 +215,46 @@ export default function UserDashboard() {
|
||||
|
||||
{/* Upcoming Appointments Section */}
|
||||
{upcomingBookings.length > 0 && (
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-4 sm:p-5 md:p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
<div className={`rounded-lg border p-4 sm:p-5 md:p-6 ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Upcoming Appointments
|
||||
</h2>
|
||||
<div className="space-y-3">
|
||||
{upcomingBookings.map((booking) => (
|
||||
<div
|
||||
key={booking.ID}
|
||||
className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow"
|
||||
className={`border rounded-lg p-4 hover:shadow-md transition-shadow ${isDark ? 'border-gray-700 bg-gray-700/50' : 'border-gray-200'}`}
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Calendar className="w-4 h-4 text-gray-600" />
|
||||
<span className="font-semibold text-gray-900">
|
||||
<Calendar className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
<span className={`font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
{formatDate(booking.scheduled_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="w-4 h-4 text-gray-600" />
|
||||
<span className="text-gray-700">
|
||||
<Clock className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
<span className={isDark ? 'text-gray-300' : 'text-gray-700'}>
|
||||
{formatTime(booking.scheduled_at)}
|
||||
</span>
|
||||
<span className="text-gray-600 text-sm font-medium">
|
||||
<span className={`text-sm font-medium ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
({booking.duration} minutes)
|
||||
</span>
|
||||
</div>
|
||||
{booking.notes && (
|
||||
<p className="text-gray-700 text-sm mt-2 font-medium">
|
||||
<p className={`text-sm mt-2 font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
{booking.notes}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:items-end gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-3 py-1 rounded-full text-sm font-medium bg-green-50 text-green-700">
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${isDark ? 'bg-green-900/30 text-green-400' : 'bg-green-50 text-green-700'}`}>
|
||||
{booking.status.charAt(0).toUpperCase() +
|
||||
booking.status.slice(1)}
|
||||
</span>
|
||||
<span className="text-lg font-bold text-gray-900">
|
||||
<span className={`text-lg font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
${booking.amount}
|
||||
</span>
|
||||
</div>
|
||||
@ -275,59 +278,59 @@ export default function UserDashboard() {
|
||||
)}
|
||||
|
||||
{/* Account Information */}
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-4 sm:p-5 md:p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
<div className={`rounded-lg border p-4 sm:p-5 md:p-6 ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}>
|
||||
<h2 className={`text-lg font-semibold mb-4 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Account Information
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-gray-50">
|
||||
<User className="w-4 h-4 text-gray-600" />
|
||||
<div className={`p-2 rounded-lg ${isDark ? 'bg-gray-700' : 'bg-gray-50'}`}>
|
||||
<User className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Full Name
|
||||
</p>
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
John Doe
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-gray-50">
|
||||
<Mail className="w-4 h-4 text-gray-600" />
|
||||
<div className={`p-2 rounded-lg ${isDark ? 'bg-gray-700' : 'bg-gray-50'}`}>
|
||||
<Mail className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Email
|
||||
</p>
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
john.doe@example.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-gray-50">
|
||||
<Phone className="w-4 h-4 text-gray-600" />
|
||||
<div className={`p-2 rounded-lg ${isDark ? 'bg-gray-700' : 'bg-gray-50'}`}>
|
||||
<Phone className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Phone
|
||||
</p>
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
+1 (555) 123-4567
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-gray-50">
|
||||
<Calendar className="w-4 h-4 text-gray-600" />
|
||||
<div className={`p-2 rounded-lg ${isDark ? 'bg-gray-700' : 'bg-gray-50'}`}>
|
||||
<Calendar className={`w-4 h-4 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">
|
||||
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Member Since
|
||||
</p>
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<p className={`text-base font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
January 2025
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -16,8 +16,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
fullName: "John Doe",
|
||||
@ -89,7 +92,7 @@ export default function SettingsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? 'bg-gray-900' : 'bg-gray-50'}`}>
|
||||
<Navbar />
|
||||
|
||||
{/* Main Content */}
|
||||
@ -98,15 +101,15 @@ export default function SettingsPage() {
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/user/dashboard">
|
||||
<Button variant="ghost" size="icon" className="hover:bg-gray-100">
|
||||
<Button variant="ghost" size="icon" className={isDark ? 'hover:bg-gray-800 text-gray-300' : 'hover:bg-gray-100'}>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900 mb-1">
|
||||
<h1 className={`text-2xl font-semibold mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
||||
Settings
|
||||
</h1>
|
||||
<p className="text-sm text-gray-600">
|
||||
<p className={`text-sm ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Manage your account settings and preferences
|
||||
</p>
|
||||
</div>
|
||||
@ -114,7 +117,7 @@ export default function SettingsPage() {
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
className="bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
className="bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
@ -128,60 +131,60 @@ export default function SettingsPage() {
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="space-y-6">
|
||||
{/* Profile Information */}
|
||||
<Card className="bg-white border-gray-200">
|
||||
<Card className={isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-5 h-5 text-gray-600" />
|
||||
<CardTitle className="text-gray-900">Profile Information</CardTitle>
|
||||
<User className={`w-5 h-5 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
<CardTitle className={isDark ? 'text-white' : 'text-gray-900'}>Profile Information</CardTitle>
|
||||
</div>
|
||||
<CardDescription className="text-gray-600">
|
||||
<CardDescription className={isDark ? 'text-gray-400' : 'text-gray-600'}>
|
||||
Update your personal information and contact details
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
Full Name
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<User className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.fullName}
|
||||
onChange={(e) => handleInputChange("fullName", e.target.value)}
|
||||
className="pl-10"
|
||||
className={`pl-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Enter your full name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
Email Address
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Mail className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
className="pl-10"
|
||||
className={`pl-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
Phone Number
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Phone className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleInputChange("phone", e.target.value)}
|
||||
className="pl-10"
|
||||
className={`pl-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Enter your phone number"
|
||||
/>
|
||||
</div>
|
||||
@ -190,34 +193,34 @@ export default function SettingsPage() {
|
||||
</Card>
|
||||
|
||||
{/* Change Password */}
|
||||
<Card className="bg-white border-gray-200">
|
||||
<Card className={isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Lock className="w-5 h-5 text-gray-600" />
|
||||
<CardTitle className="text-gray-900">Change Password</CardTitle>
|
||||
<Lock className={`w-5 h-5 ${isDark ? 'text-gray-400' : 'text-gray-600'}`} />
|
||||
<CardTitle className={isDark ? 'text-white' : 'text-gray-900'}>Change Password</CardTitle>
|
||||
</div>
|
||||
<CardDescription className="text-gray-600">
|
||||
<CardDescription className={isDark ? 'text-gray-400' : 'text-gray-600'}>
|
||||
Update your password to keep your account secure
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
Current Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Lock className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type={showPasswords.current ? "text" : "password"}
|
||||
value={passwordData.currentPassword}
|
||||
onChange={(e) => handlePasswordChange("currentPassword", e.target.value)}
|
||||
className="pl-10 pr-10"
|
||||
className={`pl-10 pr-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Enter your current password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => togglePasswordVisibility("current")}
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className={`absolute right-3 top-1/2 transform -translate-y-1/2 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-400 hover:text-gray-600'}`}
|
||||
>
|
||||
{showPasswords.current ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
@ -229,22 +232,22 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
New Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Lock className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type={showPasswords.new ? "text" : "password"}
|
||||
value={passwordData.newPassword}
|
||||
onChange={(e) => handlePasswordChange("newPassword", e.target.value)}
|
||||
className="pl-10 pr-10"
|
||||
className={`pl-10 pr-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Enter your new password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => togglePasswordVisibility("new")}
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className={`absolute right-3 top-1/2 transform -translate-y-1/2 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-400 hover:text-gray-600'}`}
|
||||
>
|
||||
{showPasswords.new ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
@ -253,28 +256,28 @@ export default function SettingsPage() {
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
<p className={`text-xs ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Password must be at least 8 characters long
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
<label className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||
Confirm New Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Lock className={`absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 ${isDark ? 'text-gray-500' : 'text-gray-400'}`} />
|
||||
<Input
|
||||
type={showPasswords.confirm ? "text" : "password"}
|
||||
value={passwordData.confirmPassword}
|
||||
onChange={(e) => handlePasswordChange("confirmPassword", e.target.value)}
|
||||
className="pl-10 pr-10"
|
||||
className={`pl-10 pr-10 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
placeholder="Confirm your new password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => togglePasswordVisibility("confirm")}
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className={`absolute right-3 top-1/2 transform -translate-y-1/2 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-400 hover:text-gray-600'}`}
|
||||
>
|
||||
{showPasswords.confirm ? (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
@ -289,7 +292,7 @@ export default function SettingsPage() {
|
||||
<Button
|
||||
onClick={handlePasswordSave}
|
||||
disabled={loading || !passwordData.currentPassword || !passwordData.newPassword || !passwordData.confirmPassword}
|
||||
className="bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
className="bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { Award, Heart, Users } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function About() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const credentials = [
|
||||
{
|
||||
@ -114,7 +101,7 @@ export function About() {
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<motion.h2
|
||||
className="text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
|
||||
className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6 px-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
@ -122,7 +109,7 @@ export function About() {
|
||||
Meet Nathalie Mac-Guffie
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
className="text-xl text-muted-foreground max-w-3xl mx-auto"
|
||||
className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
@ -133,15 +120,15 @@ export function About() {
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center mb-16">
|
||||
<div className="grid md:grid-cols-2 gap-8 md:gap-12 items-center mb-12 md:mb-16 px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={isInView ? { opacity: 1, x: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
>
|
||||
<div className="bg-gradient-to-br from-rose-100/30 via-pink-100/30 to-orange-100/30 dark:from-rose-900/20 dark:via-pink-900/20 dark:to-orange-900/20 rounded-3xl p-8 border border-border/50 backdrop-blur-sm">
|
||||
<div className="bg-gradient-to-br from-rose-100/30 via-pink-100/30 to-orange-100/30 dark:from-rose-900/20 dark:via-pink-900/20 dark:to-orange-900/20 rounded-2xl md:rounded-3xl p-6 md:p-8 border border-border/50 backdrop-blur-sm">
|
||||
<motion.h3
|
||||
className="text-2xl font-semibold mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
|
||||
className="text-xl sm:text-2xl font-semibold mb-3 md:mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isInView ? { opacity: 1 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { Users, UserCheck, Globe } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function ClientFocus() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const ages = [
|
||||
"Children (6 to 10)",
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Send } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { toast } from "sonner";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function ContactSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@ -20,15 +22,6 @@ export function ContactSection() {
|
||||
message: "",
|
||||
});
|
||||
|
||||
// Sync with global theme class like Navbar/Hero/About
|
||||
useEffect(() => {
|
||||
const sync = () => setIsDark(document.documentElement.classList.contains("dark"));
|
||||
sync();
|
||||
const observer = new MutationObserver(sync);
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
toast("Message Received", {
|
||||
@ -87,16 +80,16 @@ export function ContactSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
className="mb-16 text-center"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent px-4">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<div className="mx-auto mb-6 h-1 w-24 rounded-full bg-gradient-to-r from-rose-500 to-pink-600" />
|
||||
<p className="mx-auto max-w-2xl text-lg text-muted-foreground">
|
||||
<div className="mx-auto mb-4 sm:mb-6 h-1 w-20 sm:w-24 rounded-full bg-gradient-to-r from-rose-500 to-pink-600" />
|
||||
<p className="mx-auto max-w-2xl text-base sm:text-lg text-muted-foreground px-4">
|
||||
Ready to start your journey? Reach out to schedule a consultation.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid gap-12 lg:grid-cols-2 lg:items-stretch">
|
||||
<div className="grid gap-8 sm:gap-10 lg:gap-12 lg:grid-cols-2 lg:items-stretch">
|
||||
{/* Left: Illustration replacing cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
@ -139,8 +132,8 @@ export function ContactSection() {
|
||||
</div>
|
||||
{/* Text content */}
|
||||
<div className="flex-1 text-center sm:text-left flex flex-col justify-center">
|
||||
<h3 className="text-2xl font-bold mb-4 text-foreground">Let's Begin Your Healing Journey</h3>
|
||||
<div className="space-y-3 text-muted-foreground leading-relaxed">
|
||||
<h3 className="text-xl sm:text-2xl font-bold mb-3 sm:mb-4 text-foreground">Let's Begin Your Healing Journey</h3>
|
||||
<div className="space-y-2 sm:space-y-3 text-sm sm:text-base text-muted-foreground leading-relaxed">
|
||||
<p>
|
||||
Taking the first step toward therapy can feel daunting, but you're not alone. I'm here to support
|
||||
you through every stage of your journey toward wellness and growth.
|
||||
@ -168,8 +161,8 @@ export function ContactSection() {
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
>
|
||||
<Card className="border border-border/50 bg-card/70 backdrop-blur-sm">
|
||||
<CardContent className="p-6 md:p-8">
|
||||
<h3 className="mb-6 text-2xl font-bold text-foreground">Send a Message</h3>
|
||||
<CardContent className="p-4 sm:p-6 md:p-8">
|
||||
<h3 className="mb-4 sm:mb-6 text-xl sm:text-2xl font-bold text-foreground">Send a Message</h3>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Input
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { CreditCard, DollarSign, Shield } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Finances() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const paymentMethods = [
|
||||
"American Express",
|
||||
|
||||
@ -2,25 +2,11 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Heart, Mail, Phone, MapPin } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Footer() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const scrollToSection = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
@ -50,8 +36,8 @@ export function Footer() {
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-gray-900/40 via-gray-800/40 to-gray-900/40" />
|
||||
)}
|
||||
|
||||
<div className="container mx-auto px-4 relative z-10">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-8">
|
||||
<div className="container mx-auto px-4 sm:px-6 relative z-10">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8 mb-6 sm:mb-8">
|
||||
{/* Brand Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -163,7 +149,7 @@ export function Footer() {
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="mt-8 pt-8 border-t border-border/50"
|
||||
>
|
||||
<div className="grid grid-cols-1 items-center gap-3 text-center md:grid-cols-3 md:text-left">
|
||||
<div className="grid grid-cols-1 items-center gap-3 text-center sm:grid-cols-3 sm:text-left">
|
||||
<p className="text-sm text-muted-foreground md:justify-self-start">
|
||||
© {new Date().getFullYear()} Attune Heart Therapy. All rights reserved.
|
||||
</p>
|
||||
|
||||
@ -3,25 +3,11 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight, Calendar } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAppTheme } from '@/components/ThemeProvider';
|
||||
|
||||
export function HeroSection() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
return (
|
||||
<section
|
||||
@ -106,7 +92,7 @@ export function HeroSection() {
|
||||
transition={{ duration: 0.8 }}
|
||||
>
|
||||
<motion.h1
|
||||
className="text-5xl md:text-7xl font-bold mb-6 text-white drop-shadow-lg"
|
||||
className="text-3xl sm:text-4xl md:text-5xl lg:text-7xl font-bold mb-4 sm:mb-6 text-white drop-shadow-lg px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
@ -115,7 +101,7 @@ export function HeroSection() {
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
className="text-xl md:text-2xl text-white/95 mb-4 drop-shadow-md"
|
||||
className="text-lg sm:text-xl md:text-2xl text-white/95 mb-3 sm:mb-4 drop-shadow-md px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
@ -124,7 +110,7 @@ export function HeroSection() {
|
||||
</motion.p>
|
||||
|
||||
<motion.p
|
||||
className="text-lg md:text-xl text-white/90 mb-8 max-w-2xl mx-auto drop-shadow-md"
|
||||
className="text-base sm:text-lg md:text-xl text-white/90 mb-6 sm:mb-8 max-w-2xl mx-auto drop-shadow-md px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.6 }}
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { MapPin, Phone, Building2 } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Location() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const cities = [
|
||||
"Hollywood, FL",
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Dialog,
|
||||
@ -10,7 +11,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Eye, EyeOff, Loader2 } from "lucide-react";
|
||||
import { Eye, EyeOff, Loader2, X } from "lucide-react";
|
||||
|
||||
interface LoginDialogProps {
|
||||
open: boolean;
|
||||
@ -20,6 +21,8 @@ interface LoginDialogProps {
|
||||
|
||||
// Login Dialog component
|
||||
export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogProps) {
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [isSignup, setIsSignup] = useState(false);
|
||||
const [loginData, setLoginData] = useState({
|
||||
email: "",
|
||||
@ -89,30 +92,44 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md bg-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-3xl font-bold bg-linear-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent">
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className={`sm:max-w-md ${isDark ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}
|
||||
>
|
||||
{/* Header with Close Button */}
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<DialogHeader className="flex-1">
|
||||
<DialogTitle className="text-3xl font-bold bg-gradient-to-r from-rose-600 via-pink-600 to-rose-600 bg-clip-text text-transparent">
|
||||
{isSignup ? "Create an account" : "Welcome back"}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-gray-600">
|
||||
<DialogDescription className={isDark ? 'text-gray-400' : 'text-gray-600'}>
|
||||
{isSignup
|
||||
? "Sign up to complete your booking"
|
||||
: "Please log in to complete your booking"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center transition-colors ${isDark ? 'text-gray-400 hover:text-gray-300 hover:bg-gray-700' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'}`}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Signup Form */}
|
||||
{isSignup ? (
|
||||
<form className="space-y-6 mt-4" onSubmit={handleSignup}>
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
<div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}>
|
||||
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Full Name Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="signup-fullName" className="text-sm font-medium text-black">
|
||||
<label htmlFor="signup-fullName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Full Name *
|
||||
</label>
|
||||
<Input
|
||||
@ -121,14 +138,14 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
placeholder="John Doe"
|
||||
value={signupData.fullName}
|
||||
onChange={(e) => setSignupData({ ...signupData, fullName: e.target.value })}
|
||||
className="h-12 bg-white border-gray-300"
|
||||
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="signup-email" className="text-sm font-medium text-black">
|
||||
<label htmlFor="signup-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Email address *
|
||||
</label>
|
||||
<Input
|
||||
@ -137,14 +154,14 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
placeholder="Email address"
|
||||
value={signupData.email}
|
||||
onChange={(e) => setSignupData({ ...signupData, email: e.target.value })}
|
||||
className="h-12 bg-white border-gray-300"
|
||||
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="signup-phone" className="text-sm font-medium text-black">
|
||||
<label htmlFor="signup-phone" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Phone Number *
|
||||
</label>
|
||||
<Input
|
||||
@ -153,7 +170,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
placeholder="+1 (555) 123-4567"
|
||||
value={signupData.phone}
|
||||
onChange={(e) => setSignupData({ ...signupData, phone: e.target.value })}
|
||||
className="h-12 bg-white border-gray-300"
|
||||
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -162,7 +179,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={signupLoading}
|
||||
className="w-full h-12 text-base font-semibold bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="w-full h-12 text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{signupLoading ? (
|
||||
<>
|
||||
@ -175,12 +192,12 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
</Button>
|
||||
|
||||
{/* Switch to Login */}
|
||||
<p className="text-sm text-gray-600 text-center">
|
||||
<p className={`text-sm text-center ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
Already have an account?{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSwitchToLogin}
|
||||
className="text-blue-600 underline font-medium hover:text-blue-700"
|
||||
className={`underline font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
||||
>
|
||||
Log in
|
||||
</button>
|
||||
@ -190,14 +207,14 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
/* Login Form */
|
||||
<form className="space-y-6 mt-4" onSubmit={handleLogin}>
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
<div className={`p-3 rounded-lg border ${isDark ? 'bg-red-900/20 border-red-800' : 'bg-red-50 border-red-200'}`}>
|
||||
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Email Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="login-email" className="text-sm font-medium text-black">
|
||||
<label htmlFor="login-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Email address
|
||||
</label>
|
||||
<Input
|
||||
@ -206,14 +223,14 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
placeholder="Email address"
|
||||
value={loginData.email}
|
||||
onChange={(e) => setLoginData({ ...loginData, email: e.target.value })}
|
||||
className="h-12 bg-white border-gray-300"
|
||||
className={`h-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password Field */}
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="login-password" className="text-sm font-medium text-black">
|
||||
<label htmlFor="login-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||
Your password
|
||||
</label>
|
||||
<div className="relative">
|
||||
@ -223,7 +240,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
placeholder="Your password"
|
||||
value={loginData.password}
|
||||
onChange={(e) => setLoginData({ ...loginData, password: e.target.value })}
|
||||
className="h-12 bg-white border-gray-300 pr-12"
|
||||
className={`h-12 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
@ -231,7 +248,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 text-gray-500 hover:text-gray-700"
|
||||
className={`absolute right-4 top-1/2 -translate-y-1/2 h-auto w-auto p-0 ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'}`}
|
||||
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||
>
|
||||
{showPassword ? (
|
||||
@ -247,7 +264,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loginLoading}
|
||||
className="w-full h-12 text-base font-semibold bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="w-full h-12 text-base font-semibold bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loginLoading ? (
|
||||
<>
|
||||
@ -266,13 +283,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300 text-rose-600 focus:ring-2 focus:ring-rose-500 cursor-pointer"
|
||||
className={`w-4 h-4 rounded text-rose-600 focus:ring-2 focus:ring-rose-500 cursor-pointer ${isDark ? 'border-gray-600 bg-gray-700' : 'border-gray-300'}`}
|
||||
/>
|
||||
<span className="text-black">Remember me</span>
|
||||
<span className={isDark ? 'text-gray-300' : 'text-black'}>Remember me</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="text-blue-600 hover:text-blue-700 font-medium"
|
||||
className={`font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
Forgot password?
|
||||
@ -280,12 +297,12 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
</div>
|
||||
|
||||
{/* Sign Up Prompt */}
|
||||
<p className="text-sm text-gray-600 text-center">
|
||||
<p className={`text-sm text-center ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
New to Attune Heart Therapy?{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSwitchToSignup}
|
||||
className="text-blue-600 underline font-medium hover:text-blue-700"
|
||||
className={`underline font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
||||
>
|
||||
Sign up
|
||||
</button>
|
||||
|
||||
@ -1,46 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Heart } from "lucide-react";
|
||||
import { Heart, Menu, X } from "lucide-react";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LoginDialog } from "@/components/LoginDialog";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Navbar() {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const scrollToSection = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" });
|
||||
setMobileMenuOpen(false); // Close mobile menu after navigation
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
// Redirect to user dashboard after successful login
|
||||
router.push("/user/dashboard");
|
||||
setMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
useEffect(() => {
|
||||
if (mobileMenuOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [mobileMenuOpen]);
|
||||
|
||||
return (
|
||||
<motion.nav
|
||||
initial={{ y: -100 }}
|
||||
@ -51,61 +53,155 @@ export function Navbar() {
|
||||
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
|
||||
}}
|
||||
>
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="container mx-auto px-3 sm:px-4">
|
||||
<div className="flex items-center justify-between h-14 sm:h-16">
|
||||
<motion.div
|
||||
className="flex items-center gap-2"
|
||||
className="flex items-center gap-1.5 sm:gap-2"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<div className="bg-linear-to-r from-rose-500 to-pink-600 p-2 rounded-xl">
|
||||
<Heart className="h-5 w-5 text-white fill-white" />
|
||||
<Link href="/" className="flex items-center gap-1.5 sm:gap-2">
|
||||
<div className="bg-gradient-to-r from-rose-500 to-pink-600 p-1.5 sm:p-2 rounded-lg sm:rounded-xl">
|
||||
<Heart className="h-4 w-4 sm:h-5 sm:w-5 text-white fill-white" />
|
||||
</div>
|
||||
<span className="font-bold text-lg bg-linear-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
|
||||
Attune Heart Therapy
|
||||
<span className="font-bold text-sm sm:text-base md:text-lg bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent">
|
||||
<span className="hidden xs:inline sm:hidden">Attune Heart</span>
|
||||
<span className="hidden sm:inline">Attune Heart Therapy</span>
|
||||
<span className="xs:hidden">AHT</span>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center gap-4 xl:gap-6">
|
||||
<button
|
||||
onClick={() => scrollToSection("about")}
|
||||
className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
|
||||
className={`text-sm font-medium transition-colors cursor-pointer px-3 py-2 rounded-lg ${isDark ? 'text-gray-300 hover:text-white hover:bg-gray-800' : 'text-gray-700 hover:text-primary hover:bg-gray-100'}`}
|
||||
>
|
||||
About
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToSection("services")}
|
||||
className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
|
||||
className={`text-sm font-medium transition-colors cursor-pointer px-3 py-2 rounded-lg ${isDark ? 'text-gray-300 hover:text-white hover:bg-gray-800' : 'text-gray-700 hover:text-primary hover:bg-gray-100'}`}
|
||||
>
|
||||
Services
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToSection("contact")}
|
||||
className="text-sm font-medium hover:text-primary transition-colors cursor-pointer px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-cyan-900/30"
|
||||
className={`text-sm font-medium transition-colors cursor-pointer px-3 py-2 rounded-lg ${isDark ? 'text-gray-300 hover:text-white hover:bg-gray-800' : 'text-gray-700 hover:text-primary hover:bg-gray-100'}`}
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Desktop Actions */}
|
||||
<div className="hidden lg:flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="hidden sm:inline-flex hover:opacity-90 hover:scale-105 transition-all dark:hover:bg-cyan-900/30"
|
||||
className={`hover:opacity-90 hover:scale-105 transition-all text-xs sm:text-sm ${isDark ? 'border-gray-700 text-gray-300 hover:bg-gray-800' : ''}`}
|
||||
onClick={() => setLoginDialogOpen(true)}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<ThemeToggle />
|
||||
<Button size="sm" className="hidden sm:inline-flex hover:opacity-90 hover:scale-105 transition-all dark:hover:bg-emerald-600" asChild>
|
||||
<Button size="sm" className="hover:opacity-90 hover:scale-105 transition-all text-xs sm:text-sm" asChild>
|
||||
<a href="/book-now">Book Now</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Actions */}
|
||||
<div className="flex lg:hidden items-center gap-1.5 sm:gap-2">
|
||||
<ThemeToggle />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="hover:bg-gray-100 dark:hover:bg-gray-800 h-9 w-9 sm:h-10 sm:w-10"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{mobileMenuOpen ? (
|
||||
<X className="h-5 w-5 sm:h-6 sm:w-6" />
|
||||
) : (
|
||||
<Menu className="h-5 w-5 sm:h-6 sm:w-6" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Mobile Menu Panel */}
|
||||
<motion.div
|
||||
initial={{ x: '100%' }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: '100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="fixed top-14 sm:top-16 right-0 bottom-0 w-[280px] sm:w-80 max-w-[85vw] z-50 lg:hidden overflow-y-auto"
|
||||
style={{
|
||||
backgroundColor: isDark ? '#1a1e26' : '#ffffff'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col p-4 sm:p-6 space-y-3 sm:space-y-4">
|
||||
{/* Mobile Navigation Links */}
|
||||
<button
|
||||
onClick={() => scrollToSection("about")}
|
||||
className={`text-left text-sm sm:text-base font-medium py-2.5 sm:py-3 px-3 sm:px-4 rounded-lg transition-colors ${isDark ? 'text-gray-300 hover:bg-gray-800' : 'text-gray-700 hover:bg-gray-100'}`}
|
||||
>
|
||||
About
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToSection("services")}
|
||||
className={`text-left text-sm sm:text-base font-medium py-2.5 sm:py-3 px-3 sm:px-4 rounded-lg transition-colors ${isDark ? 'text-gray-300 hover:bg-gray-800' : 'text-gray-700 hover:bg-gray-100'}`}
|
||||
>
|
||||
Services
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToSection("contact")}
|
||||
className={`text-left text-sm sm:text-base font-medium py-2.5 sm:py-3 px-3 sm:px-4 rounded-lg transition-colors ${isDark ? 'text-gray-300 hover:bg-gray-800' : 'text-gray-700 hover:bg-gray-100'}`}
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
|
||||
<div className={`border-t pt-3 sm:pt-4 mt-3 sm:mt-4 space-y-2 sm:space-y-3 ${isDark ? 'border-gray-700' : 'border-gray-200'}`}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`w-full justify-start text-sm sm:text-base ${isDark ? 'border-gray-700 text-gray-300 hover:bg-gray-800' : ''}`}
|
||||
onClick={() => {
|
||||
setLoginDialogOpen(true);
|
||||
setMobileMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full justify-start bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white text-sm sm:text-base"
|
||||
asChild
|
||||
>
|
||||
<Link href="/book-now" onClick={() => setMobileMenuOpen(false)}>
|
||||
Book Now
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Login Dialog */}
|
||||
<LoginDialog
|
||||
open={loginDialogOpen}
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { Baby, Brain, HeartHandshake, Sparkles, Users2, Shield } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Services() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const services = [
|
||||
{
|
||||
@ -135,7 +122,7 @@ export function Services() {
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<motion.h2
|
||||
className="text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent"
|
||||
className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6 bg-gradient-to-r from-rose-600 via-pink-600 to-orange-600 bg-clip-text text-transparent px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
@ -143,7 +130,7 @@ export function Services() {
|
||||
Specialized Services
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
className="text-xl text-muted-foreground max-w-3xl mx-auto"
|
||||
className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
@ -152,7 +139,7 @@ export function Services() {
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 px-4">
|
||||
{services.map((service, index) => {
|
||||
const Icon = service.icon;
|
||||
return (
|
||||
@ -161,7 +148,7 @@ export function Services() {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="group bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 hover:border-rose-500/50 hover:shadow-lg hover:shadow-rose-500/10 hover:scale-105 transition-all duration-300 cursor-pointer"
|
||||
className="group bg-card/50 backdrop-blur-sm rounded-xl sm:rounded-2xl p-4 sm:p-6 border border-border/50 hover:border-rose-500/50 hover:shadow-lg hover:shadow-rose-500/10 hover:scale-105 transition-all duration-300 cursor-pointer"
|
||||
>
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
|
||||
@ -2,28 +2,15 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { Star, Award } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function Specialties() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkTheme = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
|
||||
checkTheme();
|
||||
const observer = new MutationObserver(checkTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const topSpecialties = [
|
||||
"Child or Adolescent",
|
||||
|
||||
@ -1,32 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const [theme, setTheme] = useState<"light" | "dark">("light");
|
||||
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null;
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const initialTheme = savedTheme || (prefersDark ? "dark" : "light");
|
||||
|
||||
setTheme(initialTheme);
|
||||
document.documentElement.classList.toggle("dark", initialTheme === "dark");
|
||||
}, []);
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme === "light" ? "dark" : "light";
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||
};
|
||||
const { theme, toggleTheme } = useAppTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleTheme}
|
||||
className="relative rounded-full cursor-pointer hover:bg-gray-100 dark:hover:bg-cyan-900/30 transition-colors"
|
||||
className="relative rounded-full cursor-pointer hover:bg-transparent transition-colors"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<Sun className={`h-5 w-5 transition-all absolute ${theme === "light" ? "rotate-0 scale-100" : "rotate-90 scale-0"}`} />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user