diff --git a/app/(admin)/_components/header.tsx b/app/(admin)/_components/header.tsx new file mode 100644 index 0000000..7bc7ac6 --- /dev/null +++ b/app/(admin)/_components/header.tsx @@ -0,0 +1,211 @@ +"use client"; + +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 { + Inbox, + Calendar, + LayoutGrid, + Heart, + UserCog, + Bell, + Settings, + LogOut, +} from "lucide-react"; + +export function Header() { + const pathname = usePathname(); + const router = useRouter(); + const [notificationsOpen, setNotificationsOpen] = useState(false); + const [userMenuOpen, setUserMenuOpen] = useState(false); + + // Mock notifications data + const notifications = [ + { + id: 1, + title: "New appointment scheduled", + message: "John Smith booked an appointment for tomorrow", + time: "2 hours ago", + type: "info", + read: false, + }, + { + id: 2, + title: "Payment received", + message: "Payment of $150 received from Jane Doe", + time: "5 hours ago", + type: "success", + read: false, + }, + ]; + + const unreadCount = notifications.filter((n) => !n.read).length; + + return ( +
+
+
+ {/* Logo */} + +
+ +
+ Attune Heart + + + {/* Navigation Links */} + + + {/* Right Side Actions */} +
+ + + + + + {/* Thumbtack Design at Top Right */} +
+
+
+
+
+

Notifications

+ {unreadCount > 0 && ( + + {unreadCount} new + + )} +
+
+ {notifications.length === 0 ? ( +
+ +

No notifications

+
+ ) : ( +
+ {notifications.map((notification) => { + return ( +
+
+
+

+ {notification.title} +

+ {!notification.read && ( + + )} +
+

+ {notification.message} +

+

+ {notification.time} +

+
+
+ ); + })} +
+ )} +
+
+ setNotificationsOpen(false)} + className="block w-full text-center text-sm font-medium text-rose-600 hover:text-rose-700 hover:underline transition-colors" + > + View all notifications + +
+
+
+ + + + + + {/* Thumbtack Design at Top Right */} +
+
+
+
+
+ + +
+
+
+
+
+
+
+ ); +} + diff --git a/app/(admin)/_components/notifications.tsx b/app/(admin)/_components/notifications.tsx new file mode 100644 index 0000000..835fe03 --- /dev/null +++ b/app/(admin)/_components/notifications.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Bell, + X, + CheckCircle, + AlertCircle, + Info, + Calendar, + Clock, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +export interface Notification { + id: string; + type: "success" | "warning" | "info" | "appointment"; + title: string; + message: string; + time: string; + read: boolean; +} + +interface NotificationsProps { + notifications: Notification[]; + onMarkAsRead?: (id: string) => void; + onDismiss?: (id: string) => void; + onMarkAllAsRead?: () => void; +} + +export function Notifications({ + notifications, + onMarkAsRead, + onDismiss, + onMarkAllAsRead, +}: NotificationsProps) { + const unreadCount = notifications.filter((n) => !n.read).length; + + const getIcon = (type: Notification["type"]) => { + switch (type) { + case "success": + return ; + case "warning": + return ; + case "info": + return ; + case "appointment": + return ; + } + }; + + const getBgColor = (type: Notification["type"]) => { + switch (type) { + case "success": + return "bg-[#4A90A4]/10 border-[#4A90A4]/30"; + case "warning": + return "bg-rose-100 border-rose-300"; + case "info": + return "bg-pink-50 border-pink-200"; + case "appointment": + return "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300"; + } + }; + + return ( +
+ {/* Header */} +
+
+ +

Notifications

+ {unreadCount > 0 && ( + + {unreadCount} + + )} +
+ {unreadCount > 0 && onMarkAllAsRead && ( + + )} +
+ + {/* Notifications List */} +
+ {notifications.length === 0 ? ( +
+ +

No notifications

+
+ ) : ( + notifications.map((notification) => ( +
+
+
{getIcon(notification.type)}
+
+
+
+

+ {notification.title} +

+

{notification.message}

+
+ + {notification.time} +
+
+
+ {!notification.read && onMarkAsRead && ( + + )} + {onDismiss && ( + + )} +
+
+
+
+
+ )) + )} +
+
+ ); +} + +// Notification Bell Component for Header +export function NotificationBell({ + count, + onClick, +}: { + count: number; + onClick: () => void; +}) { + return ( + + ); +} + diff --git a/app/(admin)/_components/side-nav.tsx b/app/(admin)/_components/side-nav.tsx new file mode 100644 index 0000000..47fade1 --- /dev/null +++ b/app/(admin)/_components/side-nav.tsx @@ -0,0 +1,164 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { + LayoutGrid, + Calendar, + Settings, + LogOut, + Menu, + X, + Heart, +} from "lucide-react"; + +const navItems = [ + { label: "Dashboard", icon: LayoutGrid, href: "/dashboard" }, + { label: "Book Appointment", icon: Calendar, href: "/booking" }, +]; + +export default function SideNav() { + const [open, setOpen] = useState(false); + const pathname = usePathname(); + const router = useRouter(); + + const getActiveIndex = () => { + return navItems.findIndex((item) => pathname?.includes(item.href)) ?? -1; + }; + + // Handle body scroll when mobile menu is open + useEffect(() => { + if (open) { + document.body.classList.add("menu-open"); + } else { + document.body.classList.remove("menu-open"); + } + return () => { + document.body.classList.remove("menu-open"); + }; + }, [open]); + + return ( + <> + {/* Mobile Top Bar */} +
+
+
+ +
+ Attune Heart Therapy +
+ +
+ + {/* Mobile Drawer Overlay */} +
setOpen(false)} + /> + + {/* Side Navigation */} + + + ); +} + diff --git a/app/(admin)/booking/page.tsx b/app/(admin)/booking/page.tsx new file mode 100644 index 0000000..4954381 --- /dev/null +++ b/app/(admin)/booking/page.tsx @@ -0,0 +1,325 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Calendar, + Clock, + User, + Video, + FileText, + MoreVertical, +} from "lucide-react"; + +interface User { + ID: number; + CreatedAt?: string; + UpdatedAt?: string; + DeletedAt?: string | null; + first_name: string; + last_name: string; + email: string; + phone: string; + location: string; + date_of_birth?: string; + is_admin?: boolean; + bookings?: null; +} + +interface Booking { + ID: number; + CreatedAt: string; + UpdatedAt: string; + DeletedAt: string | null; + user_id: number; + user: User; + scheduled_at: string; + duration: number; + status: string; + jitsi_room_id: string; + jitsi_room_url: string; + payment_id: string; + payment_status: string; + amount: number; + notes: string; +} + +interface BookingsResponse { + bookings: Booking[]; + limit: number; + offset: number; + total: number; +} + +export default function Booking() { + const [bookings, setBookings] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + // Simulate API call + const fetchBookings = async () => { + setLoading(true); + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Mock API response + const mockData: BookingsResponse = { + bookings: [ + { + ID: 1, + CreatedAt: "2025-11-06T11:33:45.704633Z", + UpdatedAt: "2025-11-06T11:33:45.707543Z", + DeletedAt: null, + user_id: 3, + user: { + ID: 3, + CreatedAt: "2025-11-06T10:43:01.299311Z", + UpdatedAt: "2025-11-06T10:43:48.427284Z", + DeletedAt: null, + first_name: "John", + last_name: "Smith", + email: "john.doe@example.com", + phone: "+1234567891", + location: "Los Angeles, CA", + date_of_birth: "0001-01-01T00:00:00Z", + is_admin: true, + bookings: null, + }, + scheduled_at: "2025-11-07T10:00:00Z", + duration: 60, + status: "scheduled", + jitsi_room_id: "booking-1-1762428825-22c92ced2870c17c", + jitsi_room_url: + "https://meet.jit.si/booking-1-1762428825-22c92ced2870c17c", + payment_id: "", + payment_status: "pending", + amount: 52, + notes: "Initial consultation session", + }, + ], + limit: 50, + offset: 0, + total: 1, + }; + + setBookings(mockData.bookings); + setLoading(false); + }; + + fetchBookings(); + }, []); + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); + }; + + const formatTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + hour12: true, + }); + }; + + const getStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case "scheduled": + return "bg-blue-100 text-blue-700"; + case "completed": + return "bg-green-100 text-green-700"; + case "cancelled": + return "bg-red-100 text-red-700"; + case "pending": + return "bg-yellow-100 text-yellow-700"; + default: + return "bg-gray-100 text-gray-700"; + } + }; + + const getPaymentStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case "paid": + return "bg-green-100 text-green-700"; + case "pending": + return "bg-yellow-100 text-yellow-700"; + case "failed": + return "bg-red-100 text-red-700"; + default: + return "bg-gray-100 text-gray-700"; + } + }; + + const filteredBookings = bookings.filter( + (booking) => + booking.user.first_name + .toLowerCase() + .includes(searchTerm.toLowerCase()) || + booking.user.last_name + .toLowerCase() + .includes(searchTerm.toLowerCase()) || + booking.user.email.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( +
+ + {/* Main Content */} +
+ {/* Page Header */} +
+
+

+ Bookings +

+

+ Manage and view all appointment bookings +

+
+ +
+ + {loading ? ( +
+
+
+ ) : filteredBookings.length === 0 ? ( +
+ +

No bookings found

+

+ {searchTerm + ? "Try adjusting your search terms" + : "Create a new booking to get started"} +

+
+ ) : ( +
+
+ + + + + + + + + + + + + + {filteredBookings.map((booking) => ( + + + + + + + + + + ))} + +
+ Patient + + Date & Time + + Duration + + Status + + Payment + + Amount + + Actions +
+
+
+ +
+
+
+ {booking.user.first_name} {booking.user.last_name} +
+
+ {booking.user.email} +
+
+ {formatDate(booking.scheduled_at)} +
+
+
+
+
+ {formatDate(booking.scheduled_at)} +
+
+ + {formatTime(booking.scheduled_at)} +
+
+ {booking.duration} min + + + {booking.status} + + + + {booking.payment_status} + + + ${booking.amount} + +
+ {booking.jitsi_room_url && ( + + + )} + {booking.notes && ( + + )} + +
+
+
+
+ )} +
+
+ ); +} + diff --git a/app/(admin)/dashboard/page.tsx b/app/(admin)/dashboard/page.tsx new file mode 100644 index 0000000..b1e9cf7 --- /dev/null +++ b/app/(admin)/dashboard/page.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Users, + UserCheck, + Calendar, + CalendarCheck, + CalendarX, + DollarSign, + TrendingUp, + ArrowUpRight, + ArrowDownRight, +} from "lucide-react"; + +interface DashboardStats { + total_users: number; + active_users: number; + total_bookings: number; + upcoming_bookings: number; + completed_bookings: number; + cancelled_bookings: number; + total_revenue: number; + monthly_revenue: number; +} + +export default function Dashboard() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [timePeriod, setTimePeriod] = useState("last_month"); + + useEffect(() => { + // Simulate API call + const fetchStats = async () => { + setLoading(true); + // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Mock API response + const mockData: DashboardStats = { + total_users: 3, + active_users: 3, + total_bookings: 6, + upcoming_bookings: 6, + completed_bookings: 0, + cancelled_bookings: 0, + total_revenue: 0, + monthly_revenue: 0, + }; + + setStats(mockData); + setLoading(false); + }; + + fetchStats(); + }, []); + + const statCards = [ + { + title: "Total Users", + value: stats?.total_users ?? 0, + icon: Users, + trend: "+12%", + trendUp: true, + }, + { + title: "Active Users", + value: stats?.active_users ?? 0, + icon: UserCheck, + trend: "+8%", + trendUp: true, + }, + { + title: "Total Bookings", + value: stats?.total_bookings ?? 0, + icon: Calendar, + trend: "+24%", + trendUp: true, + }, + { + title: "Upcoming Bookings", + value: stats?.upcoming_bookings ?? 0, + icon: CalendarCheck, + trend: "+6", + trendUp: true, + }, + { + title: "Completed Bookings", + value: stats?.completed_bookings ?? 0, + icon: CalendarCheck, + trend: "0%", + trendUp: true, + }, + { + title: "Cancelled Bookings", + value: stats?.cancelled_bookings ?? 0, + icon: CalendarX, + trend: "0%", + trendUp: false, + }, + { + title: "Total Revenue", + value: `$${stats?.total_revenue.toLocaleString() ?? 0}`, + icon: DollarSign, + trend: "+18%", + trendUp: true, + }, + { + title: "Monthly Revenue", + value: `$${stats?.monthly_revenue.toLocaleString() ?? 0}`, + icon: TrendingUp, + trend: "+32%", + trendUp: true, + }, + ]; + + + return ( +
+ + {/* Main Content */} +
+ {/* Welcome Section */} +
+
+

+ Welcome Back! Hammond +

+

+ Here's an overview of your practice today +

+
+ +
+ + {loading ? ( +
+
+
+ ) : ( + <> + {/* Stats Grid */} +
+ {statCards.map((card, index) => { + const Icon = card.icon; + return ( +
+
+
+ +
+
+ {card.trendUp ? ( + + ) : ( + + )} + {card.trend} +
+
+ +
+

+ {card.title} +

+

+ {card.value} +

+

+ vs last month +

+
+
+ ); + })} +
+ + + )} +
+
+ ); +} + diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx new file mode 100644 index 0000000..4cd0095 --- /dev/null +++ b/app/(admin)/layout.tsx @@ -0,0 +1,12 @@ +import { Header } from "./_components/header"; + +export default function AdminLayout({ children }: { children: React.ReactNode }) { + return ( +
+
+
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/app/(admin)/notifications/page.tsx b/app/(admin)/notifications/page.tsx new file mode 100644 index 0000000..fa65682 --- /dev/null +++ b/app/(admin)/notifications/page.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { useState } from "react"; +import { Bell } from "lucide-react"; + +interface Notification { + id: string; + title: string; + message: string; + time: string; + read: boolean; +} + +export default function NotificationsPage() { + const [notifications, setNotifications] = useState([ + { + id: "1", + title: "New appointment scheduled", + message: "John Smith booked an appointment for tomorrow", + time: "2 hours ago", + read: false, + }, + { + id: "2", + title: "Payment received", + message: "Payment of $150 received from Jane Doe", + time: "5 hours ago", + read: false, + }, + { + id: "3", + title: "Appointment Reminder", + message: "You have an appointment in 30 minutes with Emily Davis", + time: "3 hours ago", + read: false, + }, + { + id: "4", + title: "New Message", + message: "You received a new message from John Smith", + time: "5 hours ago", + read: true, + }, + { + id: "5", + title: "Appointment Cancelled", + message: "Robert Wilson cancelled his appointment scheduled for tomorrow", + time: "1 day ago", + read: true, + }, + ]); + + const unreadCount = notifications.filter((n) => !n.read).length; + + return ( +
+ {/* Main Content */} +
+ {/* Page Header */} +
+
+ +

+ Notifications +

+ {unreadCount > 0 && ( + + {unreadCount} new + + )} +
+
+ + {/* Notifications List */} +
+ {notifications.length === 0 ? ( +
+ +

No notifications

+
+ ) : ( +
+ {notifications.map((notification) => { + return ( +
+
+
+

+ {notification.title} +

+ {!notification.read && ( + + )} +
+

+ {notification.message} +

+

+ {notification.time} +

+
+
+ ); + })} +
+ )} +
+
+
+ ); +} + diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000..937e74a --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,7 @@ +export default function AuthLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} \ No newline at end of file diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx new file mode 100644 index 0000000..e500604 --- /dev/null +++ b/app/(auth)/login/page.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Heart, Eye, EyeOff, X } from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; + +export default function Login() { + const [showPassword, setShowPassword] = useState(false); + const [rememberMe, setRememberMe] = useState(false); + const router = useRouter(); + + return ( +
+ {/* Background Image */} +
+ Medical professionals + {/* Overlay for better readability */} +
+
+ + {/* Branding - Top Left */} +
+ + Attune Heart Therapy +
+ + + + {/* Centered White Card - Login Form */} +
+ {/* Close Button */} + + + {/* Heading */} +

+ Welcome back +

+ + {/* Sign Up Prompt */} +

+ New to Attune Heart Therapy?{" "} + + Sign up + +

+ + {/* Login Form */} +
{ + e.preventDefault(); + router.push("/dashboard"); + }}> + {/* Email Field */} +
+ + +
+ + {/* Password Field */} +
+ +
+ + +
+
+ + {/* Submit Button */} + + + {/* Remember Me & Forgot Password */} +
+ + + Forgot password? + +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index a2dc41e..9f48bd2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,94 @@ @import "tailwindcss"; +@import "tw-animate-css"; :root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; } @media (prefers-color-scheme: dark) { :root { - --background: #0a0a0a; - --foreground: #ededed; + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + border-color: hsl(var(--border)); + } + body { + background-color: #ffffff; + color: hsl(var(--foreground)); + } + html.dark body { + background-color: #1a1e26; + } + html { + scroll-behavior: smooth; + } +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } } diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..5b3bb9b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,33 +1,28 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import './globals.css'; +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import { Providers } from './providers'; +import { Toaster } from '@/components/ui/toaster'; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: 'Attune Heart Therapy | Nathalie Mac Guffie, LCSW | Miami, FL', + description: 'Compassionate, evidence-based therapy in Miami, FL. Licensed Clinical Social Worker offering anxiety, depression, trauma therapy and more.', }; export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode; -}>) { +}) { return ( - - - {children} + + + + {children} + + ); diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..273b8b5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,25 @@ -import Image from "next/image"; +import Section from "../components/Section"; +import { Footer } from "../components/Footer"; +import { HeroSection } from "@/components/Hero"; +import { About } from "@/components/About"; +import { Services } from "@/components/Services"; +import { ContactSection } from "@/components/ContactSection"; +import { Navbar } from "../components/Navbar"; + export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
+
+ + + + + + + + + +
+
); } diff --git a/app/providers.tsx b/app/providers.tsx new file mode 100644 index 0000000..9707b40 --- /dev/null +++ b/app/providers.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { ThemeProvider } from "../components/ThemeProvider"; +import { type ReactNode } from "react"; + +export function Providers({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + diff --git a/components.json b/components.json new file mode 100644 index 0000000..b7b9791 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/components/About.tsx b/components/About.tsx new file mode 100644 index 0000000..00c3d37 --- /dev/null +++ b/components/About.tsx @@ -0,0 +1,216 @@ +'use client'; + +import { motion } from "framer-motion"; +import { useInView } from "framer-motion"; +import { useRef, useEffect, useState } from "react"; +import { Award, Heart, Users } from "lucide-react"; + +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 credentials = [ + { + icon: Award, + title: "Licensed Mental Health Counselor (LMHC)", + description: "Florida licensed with 30 years of experience", + }, + { + icon: Heart, + title: "Trauma-Focused Specialist", + description: "Certified in TF-CBT for trauma recovery", + }, + { + icon: Users, + title: "Infant Mental Health & Play Therapy", + description: "Registered Play Therapist (RPT-S) and IMH Endorsement", + }, + ]; + + return ( +
+ {/* Background Image */} +
+ + {/* Subtle overlay for readability - keeping background image clearly visible */} +
+ + {/* Very subtle gradient overlay */} + {!isDark && ( +
+ )} + {isDark && ( +
+ )} + + {/* Subtle animated blobs */} +
+ + +
+ +
+ + + Meet Nathalie Mac-Guffie + + + A dedicated mental health professional specializing in helping children + under 10 and their families navigate trauma, emotional challenges, and + developmental needs. + + + +
+ +
+ + My Approach + +

+ I provide person-centered guidance, following your child's lead while + drawing out their strengths and incorporating effective coping skills. + My interventions are relationship-based, creating a warm, non-judgmental + space for growth and healing. +

+

+ Together, we'll set realistic, measurable, and achievable goals with + clear objectives tailored to your family's unique needs. +

+
+
+ + + {credentials.map((cred, index) => { + const Icon = cred.icon; + return ( + +
+ + + +
+ + {cred.title} + + + {cred.description} + +
+
+
+ ); + })} +
+
+
+
+ ); +} + diff --git a/components/ContactSection.tsx b/components/ContactSection.tsx new file mode 100644 index 0000000..dbbbeda --- /dev/null +++ b/components/ContactSection.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { motion, useInView } from "framer-motion"; +import { useEffect, 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"; + +export function ContactSection() { + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: "-100px" }); + const [isDark, setIsDark] = useState(false); + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + 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", { + description: "Thank you for reaching out. We'll get back to you soon!", + }); + setFormData({ name: "", email: "", phone: "", message: "" }); + }; + + return ( +
+ {/* Background Image */} +
+ + {/* Subtle overlay for readability - keeping background image clearly visible */} +
+ + {/* Very subtle gradient overlay */} + {!isDark && ( +
+ )} + {isDark && ( +
+ )} + + {/* Subtle animated blobs */} +
+ + +
+ +
+ +

+ Get in Touch +

+
+

+ Ready to start your journey? Reach out to schedule a consultation. +

+ + +
+ {/* Left: Illustration replacing cards */} + + + + {/* Background image for the card */} +
+ + {/* Content */} +
+ {/* Image and text side by side */} +
+ {/* Modern therapy-related illustration */} +
+ Nathalie Mac Guffie, LCSW - Your therapist + +
+ {/* Text content */} +
+

Let's Begin Your Healing Journey

+
+

+ 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. +

+

+ Whether you're seeking help for yourself or your child, I provide a warm, non-judgmental space where + healing can begin. My approach is relationship-based and tailored to meet your unique needs. +

+

+ Reach out today to schedule a consultation. Together, we can explore how therapy can support you + in creating positive change and building resilience. +

+
+
+
+
+ + + + + {/* Right: Contact form */} + + + +

Send a Message

+
+
+ setFormData({ ...formData, name: e.target.value })} + required + className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500" + /> +
+
+ setFormData({ ...formData, email: e.target.value })} + required + className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500" + /> +
+
+ setFormData({ ...formData, phone: e.target.value })} + className="bg-card text-foreground dark:text-zinc-100 placeholder:text-muted-foreground dark:placeholder:text-zinc-400 border-border focus-visible:ring-rose-500" + /> +
+
+