Compare commits
5 Commits
79a5bb9fb9
...
934f31e1f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
934f31e1f5 | ||
|
|
b068dc85ed | ||
|
|
35e654cf46 | ||
|
|
f4c3fe0c51 | ||
|
|
c0ff0386a0 |
@ -4,11 +4,7 @@ import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
Inbox,
|
||||
Calendar,
|
||||
@ -19,12 +15,16 @@ import {
|
||||
Settings,
|
||||
LogOut,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
|
||||
export function Header() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [notificationsOpen, setNotificationsOpen] = useState(false);
|
||||
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
// Mock notifications data
|
||||
const notifications = [
|
||||
@ -49,15 +49,15 @@ export function Header() {
|
||||
const unreadCount = notifications.filter((n) => !n.read).length;
|
||||
|
||||
return (
|
||||
<header className="bg-white border-b border-gray-200 fixed top-0 left-0 right-0 z-50">
|
||||
<header className={`fixed top-0 left-0 right-0 z-50 ${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border-b`}>
|
||||
<div className="px-3 sm:px-4 md:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-14 sm:h-16">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-linear-to-r from-rose-100 to-pink-100">
|
||||
<Heart className="w-4 h-4 sm:w-6 sm:h-6 text-rose-600" fill="currentColor" />
|
||||
<div className={`flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 rounded-lg ${isDark ? "bg-gray-800" : "bg-linear-to-r from-rose-100 to-pink-100"}`}>
|
||||
<Heart className={`w-4 h-4 sm:w-6 sm:h-6 ${isDark ? "text-rose-400" : "text-rose-600"}`} fill="currentColor" />
|
||||
</div>
|
||||
<span className="text-base sm:text-lg md:text-xl font-semibold text-gray-900 hidden sm:inline">Attune Heart</span>
|
||||
<span className={`text-base sm:text-lg md:text-xl font-semibold hidden sm:inline ${isDark ? "text-white" : "text-gray-900"}`}>Attune Heart</span>
|
||||
</Link>
|
||||
|
||||
{/* Navigation Links */}
|
||||
@ -67,7 +67,9 @@ export function Header() {
|
||||
className={`flex items-center gap-1 sm:gap-2 px-2 sm:px-3 md:px-4 py-1.5 sm:py-2 rounded-lg text-xs sm:text-sm font-medium transition-colors ${
|
||||
pathname === "/admin/dashboard"
|
||||
? "bg-linear-to-r from-rose-500 to-pink-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
: isDark
|
||||
? "text-gray-300 hover:bg-gray-800"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
@ -78,7 +80,9 @@ export function Header() {
|
||||
className={`flex items-center gap-1 sm:gap-2 px-2 sm:px-3 md:px-4 py-1.5 sm:py-2 rounded-lg text-xs sm:text-sm font-medium transition-colors ${
|
||||
pathname === "/admin/booking"
|
||||
? "bg-linear-to-r from-rose-500 to-pink-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
: isDark
|
||||
? "text-gray-300 hover:bg-gray-800"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
<Calendar className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
@ -88,23 +92,24 @@ export function Header() {
|
||||
|
||||
{/* Right Side Actions */}
|
||||
<div className="flex items-center gap-1.5 sm:gap-2 md:gap-3">
|
||||
<ThemeToggle />
|
||||
<Popover open={notificationsOpen} onOpenChange={setNotificationsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="relative w-8 h-8 sm:w-9 sm:h-9 md:w-10 md:h-10 cursor-pointer">
|
||||
<Inbox className="w-5 h-5 sm:w-6 sm:h-6 md:w-8 md:h-8 text-gray-600" />
|
||||
<Inbox className={`w-5 h-5 sm:w-6 sm:h-6 md:w-8 md:h-8 ${isDark ? "text-gray-300" : "text-gray-600"}`} />
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute top-0.5 right-0.5 sm:top-1 sm:right-1 w-1.5 h-1.5 sm:w-2 sm:h-2 bg-green-500 rounded-full"></span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[calc(100vw-2rem)] sm:w-80 md:w-96 p-0 bg-white shadow-xl border border-gray-200" align="end">
|
||||
<PopoverContent className={`w-[calc(100vw-2rem)] sm:w-80 md:w-96 p-0 shadow-xl border ${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"}`} align="end">
|
||||
{/* Thumbtack Design at Top Right */}
|
||||
<div className="relative">
|
||||
<div className="absolute -top-2 right-8 w-4 h-4 bg-white border-l border-t border-gray-200 rotate-45"></div>
|
||||
<div className="absolute -top-1 right-8 w-2 h-2 bg-white translate-x-1/2"></div>
|
||||
<div className={`absolute -top-2 right-8 w-4 h-4 rotate-45 ${isDark ? "bg-gray-900 border-l border-t border-gray-800" : "bg-white border-l border-t border-gray-200"}`}></div>
|
||||
<div className={`absolute -top-1 right-8 w-2 h-2 translate-x-1/2 ${isDark ? "bg-gray-900" : "bg-white"}`}></div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h3 className="font-semibold text-gray-900">Notifications</h3>
|
||||
<div className={`flex items-center justify-between p-4 border-b ${isDark ? "border-gray-800" : ""}`}>
|
||||
<h3 className={`font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>Notifications</h3>
|
||||
{unreadCount > 0 && (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-rose-100 text-rose-700 rounded-full">
|
||||
{unreadCount} new
|
||||
@ -113,24 +118,28 @@ export function Header() {
|
||||
</div>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="p-8 text-center text-gray-500">
|
||||
<Bell className="w-12 h-12 mx-auto mb-2 text-gray-300" />
|
||||
<div className={`p-8 text-center ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
<Bell className={`w-12 h-12 mx-auto mb-2 ${isDark ? "text-gray-600" : "text-gray-300"}`} />
|
||||
<p className="text-sm">No notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y">
|
||||
<div className={`divide-y ${isDark ? "divide-gray-800" : ""}`}>
|
||||
{notifications.map((notification) => {
|
||||
return (
|
||||
<div
|
||||
key={notification.id}
|
||||
className={`p-4 hover:bg-gray-50 transition-colors cursor-pointer ${
|
||||
!notification.read ? "bg-rose-50/50" : ""
|
||||
className={`p-4 transition-colors cursor-pointer ${
|
||||
!notification.read
|
||||
? isDark
|
||||
? "bg-rose-500/10"
|
||||
: "bg-rose-50/50"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p
|
||||
className={`text-sm font-medium text-gray-900 ${
|
||||
className={`text-sm font-medium ${isDark ? "text-white" : "text-gray-900"} ${
|
||||
!notification.read ? "font-semibold" : ""
|
||||
}`}
|
||||
>
|
||||
@ -140,10 +149,10 @@ export function Header() {
|
||||
<span className="shrink-0 w-2 h-2 bg-green-500 rounded-full mt-1"></span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
|
||||
{notification.message}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
<p className={`text-xs mt-1 ${isDark ? "text-gray-500" : "text-gray-400"}`}>
|
||||
{notification.time}
|
||||
</p>
|
||||
</div>
|
||||
@ -153,11 +162,11 @@ export function Header() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3 border-t bg-gray-50">
|
||||
<div className={`p-3 border-t ${isDark ? "border-gray-800 bg-gray-900/80" : "bg-gray-50"}`}>
|
||||
<Link
|
||||
href="/admin/notifications"
|
||||
onClick={() => setNotificationsOpen(false)}
|
||||
className="block w-full text-center text-sm font-medium text-rose-600 hover:text-rose-700 hover:underline transition-colors"
|
||||
className={`block w-full text-center text-sm font-medium hover:underline transition-colors ${isDark ? "text-rose-300 hover:text-rose-200" : "text-rose-600 hover:text-rose-700"}`}
|
||||
>
|
||||
View all notifications
|
||||
</Link>
|
||||
@ -166,15 +175,23 @@ export function Header() {
|
||||
</Popover>
|
||||
<Popover open={userMenuOpen} onOpenChange={setUserMenuOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="rounded-full bg-linear-to-r from-rose-100 to-pink-100 hover:from-rose-200 hover:to-pink-200 cursor-pointer w-8 h-8 sm:w-9 sm:h-9 md:w-10 md:h-10">
|
||||
<UserCog className="w-4 h-4 sm:w-5 sm:h-5 text-rose-600" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={`rounded-full cursor-pointer w-8 h-8 sm:w-9 sm:h-9 md:w-10 md:h-10 ${
|
||||
isDark
|
||||
? "bg-gray-800 hover:bg-gray-700"
|
||||
: "bg-linear-to-r from-rose-100 to-pink-100 hover:from-rose-200 hover:to-pink-200"
|
||||
}`}
|
||||
>
|
||||
<UserCog className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? "text-rose-300" : "text-rose-600"}`} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56 sm:w-64 p-0 bg-white shadow-xl border border-gray-200" align="end">
|
||||
<PopoverContent className={`w-56 sm:w-64 p-0 shadow-xl border ${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"}`} align="end">
|
||||
{/* Thumbtack Design at Top Right */}
|
||||
<div className="relative">
|
||||
<div className="absolute -top-2 right-8 w-4 h-4 bg-white border-l border-t border-gray-200 rotate-45"></div>
|
||||
<div className="absolute -top-1 right-8 w-2 h-2 bg-white translate-x-1/2"></div>
|
||||
<div className={`absolute -top-2 right-8 w-4 h-4 rotate-45 ${isDark ? "bg-gray-900 border-l border-t border-gray-800" : "bg-white border-l border-t border-gray-200"}`}></div>
|
||||
<div className={`absolute -top-1 right-8 w-2 h-2 translate-x-1/2 ${isDark ? "bg-gray-900" : "bg-white"}`}></div>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Button
|
||||
@ -183,10 +200,12 @@ export function Header() {
|
||||
setUserMenuOpen(false);
|
||||
router.push("/admin/settings");
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 justify-start hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 justify-start transition-colors cursor-pointer ${
|
||||
isDark ? "hover:bg-gray-800" : "hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<Settings className="w-5 h-5 text-gray-600" />
|
||||
<span className="text-sm font-medium text-gray-900">Settings</span>
|
||||
<Settings className={`w-5 h-5 ${isDark ? "text-gray-300" : "text-gray-600"}`} />
|
||||
<span className={`text-sm font-medium ${isDark ? "text-white" : "text-gray-900"}`}>Settings</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@ -194,7 +213,9 @@ export function Header() {
|
||||
setUserMenuOpen(false);
|
||||
router.push("/");
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 justify-start hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 justify-start transition-colors cursor-pointer ${
|
||||
isDark ? "hover:bg-gray-800" : "hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<LogOut className="w-5 h-5 text-red-500" />
|
||||
<span className="text-sm font-medium text-red-500">Logout</span>
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
@ -36,30 +37,32 @@ export function Notifications({
|
||||
onMarkAllAsRead,
|
||||
}: NotificationsProps) {
|
||||
const unreadCount = notifications.filter((n) => !n.read).length;
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const getIcon = (type: Notification["type"]) => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return <CheckCircle className="w-5 h-5 text-green-600" />;
|
||||
return <CheckCircle className={`w-5 h-5 ${isDark ? "text-green-300" : "text-green-600"}`} />;
|
||||
case "warning":
|
||||
return <AlertCircle className="w-5 h-5 text-orange-600" />;
|
||||
return <AlertCircle className={`w-5 h-5 ${isDark ? "text-orange-300" : "text-orange-600"}`} />;
|
||||
case "info":
|
||||
return <Info className="w-5 h-5 text-blue-600" />;
|
||||
return <Info className={`w-5 h-5 ${isDark ? "text-blue-300" : "text-blue-600"}`} />;
|
||||
case "appointment":
|
||||
return <Calendar className="w-5 h-5 text-rose-600" />;
|
||||
return <Calendar className={`w-5 h-5 ${isDark ? "text-rose-300" : "text-rose-600"}`} />;
|
||||
}
|
||||
};
|
||||
|
||||
const getBgColor = (type: Notification["type"]) => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return "bg-[#4A90A4]/10 border-[#4A90A4]/30";
|
||||
return isDark ? "bg-green-500/10 border-green-500/30" : "bg-[#4A90A4]/10 border-[#4A90A4]/30";
|
||||
case "warning":
|
||||
return "bg-rose-100 border-rose-300";
|
||||
return isDark ? "bg-rose-500/10 border-rose-400/40" : "bg-rose-100 border-rose-300";
|
||||
case "info":
|
||||
return "bg-pink-50 border-pink-200";
|
||||
return isDark ? "bg-pink-500/10 border-pink-400/40" : "bg-pink-50 border-pink-200";
|
||||
case "appointment":
|
||||
return "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300";
|
||||
return isDark ? "bg-rose-500/10 border-rose-400/40" : "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300";
|
||||
}
|
||||
};
|
||||
|
||||
@ -68,8 +71,8 @@ export function Notifications({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Bell className="w-6 h-6 text-gray-900" />
|
||||
<h2 className="text-2xl font-bold text-gray-900">Notifications</h2>
|
||||
<Bell className={`w-6 h-6 ${isDark ? "text-white" : "text-gray-900"}`} />
|
||||
<h2 className={`text-2xl font-bold ${isDark ? "text-white" : "text-gray-900"}`}>Notifications</h2>
|
||||
{unreadCount > 0 && (
|
||||
<span className="px-2.5 py-0.5 bg-linear-to-r from-rose-500 to-pink-500 text-white text-sm font-medium rounded-full">
|
||||
{unreadCount}
|
||||
@ -77,7 +80,12 @@ export function Notifications({
|
||||
)}
|
||||
</div>
|
||||
{unreadCount > 0 && onMarkAllAsRead && (
|
||||
<Button variant="outline" size="sm" onClick={onMarkAllAsRead}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onMarkAllAsRead}
|
||||
className={isDark ? "border-gray-700 text-gray-200 hover:bg-gray-800" : ""}
|
||||
>
|
||||
Mark all as read
|
||||
</Button>
|
||||
)}
|
||||
@ -87,8 +95,8 @@ export function Notifications({
|
||||
<div className="space-y-3">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Bell className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600">No notifications</p>
|
||||
<Bell className={`w-12 h-12 mx-auto mb-4 ${isDark ? "text-gray-600" : "text-gray-400"}`} />
|
||||
<p className={isDark ? "text-gray-400" : "text-gray-600"}>No notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
notifications.map((notification) => (
|
||||
@ -97,7 +105,7 @@ export function Notifications({
|
||||
className={cn(
|
||||
"p-4 rounded-lg border-2 transition-all",
|
||||
getBgColor(notification.type),
|
||||
!notification.read && "ring-2 ring-offset-2 ring-rose-300"
|
||||
!notification.read && (isDark ? "ring-2 ring-offset-2 ring-rose-400 ring-offset-gray-900" : "ring-2 ring-offset-2 ring-rose-300")
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
@ -108,13 +116,15 @@ export function Notifications({
|
||||
<h3
|
||||
className={cn(
|
||||
"font-semibold mb-1",
|
||||
!notification.read ? "text-gray-900" : "text-gray-700"
|
||||
!notification.read
|
||||
? isDark ? "text-white" : "text-gray-900"
|
||||
: isDark ? "text-gray-300" : "text-gray-700"
|
||||
)}
|
||||
>
|
||||
{notification.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-2">{notification.message}</p>
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<p className={`text-sm mb-2 ${isDark ? "text-gray-400" : "text-gray-600"}`}>{notification.message}</p>
|
||||
<div className={`flex items-center gap-2 text-xs ${isDark ? "text-gray-500" : "text-gray-500"}`}>
|
||||
<Clock className="w-3 h-3" />
|
||||
{notification.time}
|
||||
</div>
|
||||
@ -125,7 +135,7 @@ export function Notifications({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => onMarkAsRead(notification.id)}
|
||||
className="h-7 w-7"
|
||||
className={`h-7 w-7 ${isDark ? "text-gray-300 hover:bg-gray-800" : ""}`}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
@ -135,7 +145,7 @@ export function Notifications({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => onDismiss(notification.id)}
|
||||
className="h-7 w-7"
|
||||
className={`h-7 w-7 ${isDark ? "text-gray-300 hover:bg-gray-800" : ""}`}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
X,
|
||||
Heart,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
const navItems = [
|
||||
{ label: "Dashboard", icon: LayoutGrid, href: "/admin/dashboard" },
|
||||
@ -23,6 +24,8 @@ export default function SideNav() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const getActiveIndex = () => {
|
||||
return navItems.findIndex((item) => pathname?.includes(item.href)) ?? -1;
|
||||
@ -43,12 +46,12 @@ export default function SideNav() {
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Top Bar */}
|
||||
<div className="flex md:hidden items-center justify-between px-4 py-3 border-b border-gray-200 bg-white z-30 fixed top-0 left-0 right-0">
|
||||
<div className={`flex md:hidden items-center justify-between px-4 py-3 border-b z-30 fixed top-0 left-0 right-0 ${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"}`}>
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-linear-to-r from-rose-100 to-pink-100">
|
||||
<Heart className="w-5 h-5 text-rose-600" fill="currentColor" />
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-lg ${isDark ? "bg-gray-800" : "bg-linear-to-r from-rose-100 to-pink-100"}`}>
|
||||
<Heart className={`w-5 h-5 ${isDark ? "text-rose-300" : "text-rose-600"}`} fill="currentColor" />
|
||||
</div>
|
||||
<span className="text-lg font-semibold text-gray-900">Attune Heart Therapy</span>
|
||||
<span className={`text-lg font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>Attune Heart Therapy</span>
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@ -70,21 +73,21 @@ export default function SideNav() {
|
||||
|
||||
{/* Side Navigation */}
|
||||
<aside
|
||||
className={`fixed top-0 left-0 z-50 h-screen bg-white border-r border-gray-200 flex flex-col transition-transform duration-200 w-[85vw] max-w-[200px] min-w-[160px] md:translate-x-0 md:w-[200px] md:min-w-[200px] md:max-w-[200px] ${
|
||||
className={`fixed top-0 left-0 z-50 h-screen flex flex-col transition-transform duration-200 w-[85vw] max-w-[200px] min-w-[160px] md:translate-x-0 md:w-[200px] md:min-w-[200px] md:max-w-[200px] ${isDark ? "bg-gray-900 border-r border-gray-800" : "bg-white border-r border-gray-200"} ${
|
||||
open ? "translate-x-0" : "-translate-x-full"
|
||||
} md:translate-x-0`}
|
||||
>
|
||||
{/* Logo Section */}
|
||||
<div className="shrink-0 px-3 pb-4 flex flex-col gap-1 md:block mb-5 pt-16 md:pt-4">
|
||||
<Link href="/" className="flex items-center gap-2 mb-1 ml-2 md:ml-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-linear-to-r from-rose-100 to-pink-100">
|
||||
<Heart className="w-5 h-5 text-rose-600" fill="currentColor" />
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-lg ${isDark ? "bg-gray-800" : "bg-linear-to-r from-rose-100 to-pink-100"}`}>
|
||||
<Heart className={`w-5 h-5 ${isDark ? "text-rose-300" : "text-rose-600"}`} fill="currentColor" />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-gray-900">Attune Heart</span>
|
||||
<span className={`text-sm font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>Attune Heart</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<hr className="shrink-0 -mt-10 mb-4 mx-3 border-gray-200 md:block hidden" />
|
||||
<hr className={`shrink-0 -mt-10 mb-4 mx-3 md:block hidden ${isDark ? "border-gray-800" : "border-gray-200"}`} />
|
||||
|
||||
{/* Navigation Items */}
|
||||
<nav className="flex-1 overflow-y-auto flex flex-col gap-2 px-2 md:px-0">
|
||||
@ -106,7 +109,9 @@ export default function SideNav() {
|
||||
className={`group flex items-center gap-2 py-2.5 pl-3 md:pl-3 pr-3 md:pr-3 transition-colors duration-200 focus:outline-none w-[90%] md:w-[90%] ml-1 md:ml-2 cursor-pointer justify-start ${
|
||||
isActive
|
||||
? "bg-linear-to-r from-rose-500 to-pink-600 text-white border border-rose-500 rounded-[5px] shadow-sm"
|
||||
: "bg-transparent text-gray-600 hover:bg-rose-50 hover:text-rose-600 rounded-lg"
|
||||
: isDark
|
||||
? "bg-transparent text-gray-300 hover:bg-gray-800 hover:text-rose-300 rounded-lg"
|
||||
: "bg-transparent text-gray-600 hover:bg-rose-50 hover:text-rose-600 rounded-lg"
|
||||
}`}
|
||||
style={isActive ? { height: 40 } : {}}
|
||||
>
|
||||
@ -116,7 +121,9 @@ export default function SideNav() {
|
||||
className={
|
||||
isActive
|
||||
? "text-white"
|
||||
: "text-gray-700 group-hover:text-rose-600"
|
||||
: isDark
|
||||
? "text-gray-400 group-hover:text-rose-300"
|
||||
: "text-gray-700 group-hover:text-rose-600"
|
||||
}
|
||||
/>
|
||||
<span
|
||||
@ -131,7 +138,7 @@ export default function SideNav() {
|
||||
})}
|
||||
|
||||
{/* Bottom Actions */}
|
||||
<div className="mt-auto pt-4 pb-4 border-t border-gray-200">
|
||||
<div className={`mt-auto pt-4 pb-4 border-t ${isDark ? "border-gray-800" : "border-gray-200"}`}>
|
||||
<div className="relative flex items-center w-full">
|
||||
{pathname === "/admin/settings" && (
|
||||
<span
|
||||
@ -145,7 +152,9 @@ export default function SideNav() {
|
||||
className={`group flex items-center gap-2 py-2.5 pl-3 md:pl-3 pr-3 md:pr-3 transition-colors duration-200 w-[90%] md:w-[90%] ml-1 md:ml-2 cursor-pointer justify-start rounded-lg ${
|
||||
pathname === "/admin/settings"
|
||||
? "bg-linear-to-r from-rose-500 to-pink-600 text-white border border-rose-500 rounded-[5px] shadow-sm"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||
: isDark
|
||||
? "text-gray-300 hover:bg-gray-800 hover:text-rose-300"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||
}`}
|
||||
style={pathname === "/admin/settings" ? { height: 40 } : {}}
|
||||
>
|
||||
@ -155,7 +164,9 @@ export default function SideNav() {
|
||||
className={
|
||||
pathname === "/admin/settings"
|
||||
? "text-white"
|
||||
: "text-gray-700 group-hover:text-gray-900"
|
||||
: isDark
|
||||
? "text-gray-400 group-hover:text-rose-300"
|
||||
: "text-gray-700 group-hover:text-gray-900"
|
||||
}
|
||||
/>
|
||||
<span className="font-light leading-none text-[12px]" style={{ fontWeight: 300 }}>
|
||||
@ -169,9 +180,13 @@ export default function SideNav() {
|
||||
setOpen(false);
|
||||
router.push("/");
|
||||
}}
|
||||
className="group flex items-center gap-2 py-2.5 pl-3 md:pl-3 pr-3 md:pr-3 transition-colors duration-200 w-[90%] md:w-[90%] ml-1 md:ml-2 cursor-pointer justify-start text-gray-600 hover:bg-gray-50 hover:text-gray-900 rounded-lg"
|
||||
className={`group flex items-center gap-2 py-2.5 pl-3 md:pl-3 pr-3 md:pr-3 transition-colors duration-200 w-[90%] md:w-[90%] ml-1 md:ml-2 cursor-pointer justify-start rounded-lg ${
|
||||
isDark
|
||||
? "text-gray-300 hover:bg-gray-800 hover:text-rose-300"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<LogOut size={18} strokeWidth={1.5} className="text-gray-700 group-hover:text-gray-900" />
|
||||
<LogOut size={18} strokeWidth={1.5} className={isDark ? "text-gray-400 group-hover:text-rose-300" : "text-gray-700 group-hover:text-gray-900"} />
|
||||
<span className="font-light leading-none text-[12px]" style={{ fontWeight: 300 }}>
|
||||
Logout
|
||||
</span>
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
FileText,
|
||||
MoreVertical,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
interface User {
|
||||
ID: number;
|
||||
@ -54,6 +55,8 @@ export default function Booking() {
|
||||
const [bookings, setBookings] = useState<Booking[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate API call
|
||||
@ -127,7 +130,22 @@ export default function Booking() {
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status.toLowerCase()) {
|
||||
const normalized = status.toLowerCase();
|
||||
if (isDark) {
|
||||
switch (normalized) {
|
||||
case "scheduled":
|
||||
return "bg-blue-500/20 text-blue-200";
|
||||
case "completed":
|
||||
return "bg-green-500/20 text-green-200";
|
||||
case "cancelled":
|
||||
return "bg-red-500/20 text-red-200";
|
||||
case "pending":
|
||||
return "bg-yellow-500/20 text-yellow-200";
|
||||
default:
|
||||
return "bg-gray-700 text-gray-200";
|
||||
}
|
||||
}
|
||||
switch (normalized) {
|
||||
case "scheduled":
|
||||
return "bg-blue-100 text-blue-700";
|
||||
case "completed":
|
||||
@ -142,7 +160,20 @@ export default function Booking() {
|
||||
};
|
||||
|
||||
const getPaymentStatusColor = (status: string) => {
|
||||
switch (status.toLowerCase()) {
|
||||
const normalized = status.toLowerCase();
|
||||
if (isDark) {
|
||||
switch (normalized) {
|
||||
case "paid":
|
||||
return "bg-green-500/20 text-green-200";
|
||||
case "pending":
|
||||
return "bg-yellow-500/20 text-yellow-200";
|
||||
case "failed":
|
||||
return "bg-red-500/20 text-red-200";
|
||||
default:
|
||||
return "bg-gray-700 text-gray-200";
|
||||
}
|
||||
}
|
||||
switch (normalized) {
|
||||
case "paid":
|
||||
return "bg-green-100 text-green-700";
|
||||
case "pending":
|
||||
@ -166,102 +197,104 @@ export default function Booking() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="p-3 sm:p-4 md:p-6 lg:p-8">
|
||||
{/* Page Header */}
|
||||
<div className="mb-4 sm:mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||
<div>
|
||||
<h1 className="text-xl sm: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"}`}>
|
||||
Bookings
|
||||
</h1>
|
||||
<p className="text-xs sm:text-sm text-gray-500">
|
||||
<p className={`text-xs sm:text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Manage and view all appointment bookings
|
||||
</p>
|
||||
</div>
|
||||
<button className="w-full sm:w-auto px-3 sm:px-4 py-2 bg-gray-900 text-white rounded-lg text-xs sm:text-sm font-medium hover:bg-gray-800 transition-colors">
|
||||
<button className={`w-full sm:w-auto px-3 sm:px-4 py-2 rounded-lg text-xs sm:text-sm font-medium transition-colors ${
|
||||
isDark ? "bg-rose-500 text-white hover:bg-rose-600" : "bg-gray-900 text-white hover:bg-gray-800"
|
||||
}`}>
|
||||
+ New Booking
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
) : filteredBookings.length === 0 ? (
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-12 text-center">
|
||||
<Calendar className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600 font-medium mb-1">No bookings found</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
<div className={`rounded-lg border p-12 text-center ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||
<Calendar className={`w-12 h-12 mx-auto mb-4 ${isDark ? "text-gray-500" : "text-gray-400"}`} />
|
||||
<p className={`font-medium mb-1 ${isDark ? "text-gray-200" : "text-gray-600"}`}>No bookings found</p>
|
||||
<p className={`text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
{searchTerm
|
||||
? "Try adjusting your search terms"
|
||||
: "Create a new booking to get started"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div className={`rounded-lg border overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<thead className={`${isDark ? "bg-gray-800 border-b border-gray-700" : "bg-gray-50 border-b border-gray-200"}`}>
|
||||
<tr>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Patient
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden md:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Date & Time
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Duration
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Status
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden lg:table-cell">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden lg:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Payment
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden xl:table-cell">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-left text-xs font-medium uppercase tracking-wider hidden xl:table-cell ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Amount
|
||||
</th>
|
||||
<th className="px-3 sm:px-4 md:px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th className={`px-3 sm:px-4 md:px-6 py-3 text-right text-xs font-medium uppercase tracking-wider ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
<tbody className={`${isDark ? "bg-gray-800 divide-gray-700" : "bg-white divide-gray-200"}`}>
|
||||
{filteredBookings.map((booking) => (
|
||||
<tr
|
||||
key={booking.ID}
|
||||
className="hover:bg-gray-50 transition-colors"
|
||||
className={`transition-colors ${isDark ? "hover:bg-gray-700" : "hover:bg-gray-50"}`}
|
||||
>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<div className="shrink-0 h-8 w-8 sm:h-10 sm:w-10 rounded-full bg-gray-100 flex items-center justify-center">
|
||||
<User className="w-4 h-4 sm:w-5 sm:h-5 text-gray-600" />
|
||||
<div className={`shrink-0 h-8 w-8 sm:h-10 sm:w-10 rounded-full flex items-center justify-center ${isDark ? "bg-gray-700" : "bg-gray-100"}`}>
|
||||
<User className={`w-4 h-4 sm:w-5 sm:h-5 ${isDark ? "text-gray-200" : "text-gray-600"}`} />
|
||||
</div>
|
||||
<div className="ml-2 sm:ml-4 min-w-0">
|
||||
<div className="text-xs sm:text-sm font-medium text-gray-900 truncate">
|
||||
<div className={`text-xs sm:text-sm font-medium truncate ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||
{booking.user.first_name} {booking.user.last_name}
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm text-gray-500 truncate hidden sm:block">
|
||||
<div className={`text-xs sm:text-sm truncate hidden sm:block ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
{booking.user.email}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 sm:hidden mt-0.5">
|
||||
<div className={`text-xs sm:hidden mt-0.5 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
{formatDate(booking.scheduled_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap hidden md:table-cell">
|
||||
<div className="text-xs sm:text-sm text-gray-900">
|
||||
<div className={`text-xs sm:text-sm ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||
{formatDate(booking.scheduled_at)}
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm text-gray-500 flex items-center gap-1">
|
||||
<div className={`text-xs sm:text-sm flex items-center gap-1 ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
<Clock className="w-3 h-3" />
|
||||
{formatTime(booking.scheduled_at)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm text-gray-900 hidden lg:table-cell">
|
||||
<td className={`px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm hidden lg:table-cell ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||
{booking.duration} min
|
||||
</td>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap">
|
||||
@ -282,7 +315,7 @@ export default function Booking() {
|
||||
{booking.payment_status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm font-medium text-gray-900 hidden xl:table-cell">
|
||||
<td className={`px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-xs sm:text-sm font-medium hidden xl:table-cell ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||
${booking.amount}
|
||||
</td>
|
||||
<td className="px-3 sm:px-4 md:px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
@ -292,7 +325,7 @@ export default function Booking() {
|
||||
href={booking.jitsi_room_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1.5 sm:p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
className={`p-1.5 sm:p-2 rounded-lg transition-colors ${isDark ? "text-gray-300 hover:text-white hover:bg-gray-700" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}`}
|
||||
title="Join Meeting"
|
||||
>
|
||||
<Video className="w-4 h-4" />
|
||||
@ -300,13 +333,13 @@ export default function Booking() {
|
||||
)}
|
||||
{booking.notes && (
|
||||
<button
|
||||
className="p-1.5 sm:p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
className={`p-1.5 sm:p-2 rounded-lg transition-colors ${isDark ? "text-gray-300 hover:text-white hover:bg-gray-700" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}`}
|
||||
title="View Notes"
|
||||
>
|
||||
<FileText className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button className="p-1.5 sm:p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors">
|
||||
<button className={`p-1.5 sm:p-2 rounded-lg transition-colors ${isDark ? "text-gray-300 hover:text-white hover:bg-gray-700" : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"}`}>
|
||||
<MoreVertical className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
interface DashboardStats {
|
||||
total_users: number;
|
||||
@ -35,6 +36,8 @@ export default function Dashboard() {
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [timePeriod, setTimePeriod] = useState<string>("last_month");
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate API call
|
||||
@ -122,36 +125,43 @@ export default function Dashboard() {
|
||||
];
|
||||
|
||||
|
||||
const getTrendClasses = (trendUp: boolean) => {
|
||||
if (isDark) {
|
||||
return trendUp ? "bg-green-500/20 text-green-200" : "bg-red-500/20 text-red-200";
|
||||
}
|
||||
return trendUp ? "bg-green-50 text-green-700" : "bg-red-50 text-red-700";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="p-4 sm:p-6 lg:p-8 space-y-6">
|
||||
{/* Welcome Section */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 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! Hammond
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className={`text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
Here's an overview of your practice today
|
||||
</p>
|
||||
</div>
|
||||
<Select value={timePeriod} onValueChange={setTimePeriod}>
|
||||
<SelectTrigger className="w-[180px] cursor-pointer">
|
||||
<SelectTrigger className={`w-full sm:w-[200px] cursor-pointer ${isDark ? "bg-gray-800 border-gray-700 text-gray-100" : "bg-white border-gray-200 text-gray-900"}`}>
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white cursor-pointer">
|
||||
<SelectItem value="last_week">Last Week</SelectItem>
|
||||
<SelectItem value="last_month">Last Month</SelectItem>
|
||||
<SelectItem value="last_year">Last Year</SelectItem>
|
||||
<SelectContent className={`${isDark ? "bg-gray-800 border-gray-700 text-gray-100" : "bg-white border-gray-200 text-gray-900"} cursor-pointer`}>
|
||||
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_week">Last Week</SelectItem>
|
||||
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_month">Last Month</SelectItem>
|
||||
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_year">Last Year</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
) : (
|
||||
<>
|
||||
@ -162,17 +172,13 @@ export default function Dashboard() {
|
||||
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-200" : "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"
|
||||
}`}>
|
||||
<div className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium ${getTrendClasses(card.trendUp)}`}>
|
||||
{card.trendUp ? (
|
||||
<ArrowUpRight className="w-3 h-3" />
|
||||
) : (
|
||||
@ -183,13 +189,13 @@ export default function Dashboard() {
|
||||
</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-300" : "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-500">
|
||||
<p className={`text-xs ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
vs last month
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { Bell } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
interface Notification {
|
||||
id: string;
|
||||
@ -51,16 +52,18 @@ export default function NotificationsPage() {
|
||||
]);
|
||||
|
||||
const unreadCount = notifications.filter((n) => !n.read).length;
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
||||
{/* Main Content */}
|
||||
<main className="p-3 sm:p-4 md:p-6 lg:p-8">
|
||||
{/* Page Header */}
|
||||
<div className="mb-4 sm:mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Bell className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
|
||||
<h1 className="text-xl sm:text-2xl font-semibold text-gray-900">
|
||||
<Bell className={`w-5 h-5 sm:w-6 sm:h-6 ${isDark ? "text-white" : "text-gray-900"}`} />
|
||||
<h1 className={`text-xl sm:text-2xl font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||
Notifications
|
||||
</h1>
|
||||
{unreadCount > 0 && (
|
||||
@ -72,26 +75,30 @@ export default function NotificationsPage() {
|
||||
</div>
|
||||
|
||||
{/* Notifications List */}
|
||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<div className={`rounded-lg border overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||
{notifications.length === 0 ? (
|
||||
<div className="p-8 sm:p-12 text-center text-gray-500">
|
||||
<Bell className="w-12 h-12 mx-auto mb-2 text-gray-300" />
|
||||
<div className={`p-8 sm:p-12 text-center ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||
<Bell className={`w-12 h-12 mx-auto mb-2 ${isDark ? "text-gray-600" : "text-gray-300"}`} />
|
||||
<p className="text-sm">No notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y">
|
||||
<div className={`divide-y ${isDark ? "divide-gray-700" : ""}`}>
|
||||
{notifications.map((notification) => {
|
||||
return (
|
||||
<div
|
||||
key={notification.id}
|
||||
className={`p-4 sm:p-6 hover:bg-gray-50 transition-colors cursor-pointer ${
|
||||
!notification.read ? "bg-rose-50/50" : ""
|
||||
className={`p-4 sm:p-6 transition-colors cursor-pointer ${
|
||||
!notification.read
|
||||
? isDark
|
||||
? "bg-rose-500/10"
|
||||
: "bg-rose-50/50"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p
|
||||
className={`text-sm sm:text-base font-medium text-gray-900 ${
|
||||
className={`text-sm sm:text-base font-medium ${isDark ? "text-white" : "text-gray-900"} ${
|
||||
!notification.read ? "font-semibold" : ""
|
||||
}`}
|
||||
>
|
||||
@ -101,10 +108,10 @@ export default function NotificationsPage() {
|
||||
<span className="shrink-0 w-2 h-2 bg-green-500 rounded-full mt-1"></span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
<p className={`text-sm mt-1 ${isDark ? "text-gray-400" : "text-gray-600"}`}>
|
||||
{notification.message}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
<p className={`text-xs mt-1 ${isDark ? "text-gray-500" : "text-gray-400"}`}>
|
||||
{notification.time}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
EyeOff,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
|
||||
export default function AdminSettingsPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -33,6 +34,8 @@ export default function AdminSettingsPage() {
|
||||
new: false,
|
||||
confirm: false,
|
||||
});
|
||||
const { theme } = useAppTheme();
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData((prev) => ({
|
||||
@ -88,22 +91,22 @@ export default function AdminSettingsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
||||
{/* Main Content */}
|
||||
<main className="p-4 sm:p-6 lg:p-8 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/admin/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 practice information
|
||||
</p>
|
||||
</div>
|
||||
@ -111,7 +114,7 @@ export default function AdminSettingsPage() {
|
||||
<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="w-full sm:w-auto bg-linear-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>
|
||||
@ -125,60 +128,60 @@ export default function AdminSettingsPage() {
|
||||
<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-300" : "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" : ""}`}
|
||||
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" : ""}`}
|
||||
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" : ""}`}
|
||||
placeholder="Enter your phone number"
|
||||
/>
|
||||
</div>
|
||||
@ -187,34 +190,34 @@ export default function AdminSettingsPage() {
|
||||
</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-300" : "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" : ""}`}
|
||||
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" />
|
||||
@ -226,22 +229,22 @@ export default function AdminSettingsPage() {
|
||||
</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" : ""}`}
|
||||
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" />
|
||||
@ -250,28 +253,28 @@ export default function AdminSettingsPage() {
|
||||
)}
|
||||
</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" : ""}`}
|
||||
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" />
|
||||
|
||||
@ -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">
|
||||
{/* Close Button */}
|
||||
<Button
|
||||
onClick={() => router.back()}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-auto mb-6 w-8 h-8 rounded-full"
|
||||
aria-label="Close"
|
||||
>
|
||||
</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 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={`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>
|
||||
</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,34 +128,34 @@ 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 */}
|
||||
<main className="container mx-auto px-4 sm:px-6 lg:px-8 space-y-6 pt-20 sm:pt-24 pb-8">
|
||||
{/* Welcome Section */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 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>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/user/settings">
|
||||
<Link href="/user/settings" className="flex-1 sm:flex-initial">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:bg-gray-100"
|
||||
className={`w-full sm:w-auto ${isDark ? 'hover:bg-gray-800 border-gray-700 text-gray-300' : 'hover:bg-gray-100'}`}
|
||||
>
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Settings
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/book-now">
|
||||
<Link href="/book-now" className="flex-1 sm:flex-initial">
|
||||
<Button
|
||||
className="bg-linear-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white"
|
||||
className="w-full sm:w-auto 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,24 +92,24 @@ 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 */}
|
||||
<main className="container mx-auto px-4 sm:px-6 lg:px-8 space-y-6 pt-20 sm:pt-24 pb-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 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="w-full sm:w-auto 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,19 @@ 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 }}
|
||||
>
|
||||
<<<<<<< HEAD
|
||||
<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">
|
||||
=======
|
||||
<div className="bg-gradient-to-br from-rose-100/15 via-pink-100/15 to-orange-100/15 dark:from-rose-900/15 dark:via-pink-900/15 dark:to-orange-900/15 rounded-3xl p-8 border border-border/50 backdrop-blur-sm">
|
||||
>>>>>>> 79a5bb9fb99183cdae6841a62b115a1f8b88161e
|
||||
<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, LLC. 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">
|
||||
{isSignup ? "Create an account" : "Welcome back"}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-gray-600">
|
||||
{isSignup
|
||||
? "Sign up to complete your booking"
|
||||
: "Please log in to complete your booking"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<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={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,20 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useInView } from "framer-motion";
|
||||
<<<<<<< HEAD
|
||||
import { useRef } from "react";
|
||||
import { Baby, Brain, HeartHandshake, Sparkles, Users2, Shield } from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
=======
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { Users, Sparkles, Heart, Users2, Feather, Shield } from "lucide-react";
|
||||
>>>>>>> 79a5bb9fb99183cdae6841a62b115a1f8b88161e
|
||||
|
||||
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 = [
|
||||
{
|
||||
@ -141,7 +133,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 }}
|
||||
@ -149,7 +141,7 @@ export function Services() {
|
||||
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 }}
|
||||
@ -158,7 +150,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 (
|
||||
@ -167,7 +159,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"
|
||||
>
|
||||
<div className="relative z-10">
|
||||
<motion.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 { 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