Compare commits
No commits in common. "7d7d346bd9bdca7602765077b0f72db7ceb19836" and "f5e867414bb0bf08c21416295a73267561bc234b" have entirely different histories.
7d7d346bd9
...
f5e867414b
@ -14,7 +14,6 @@ import {
|
|||||||
Bell,
|
Bell,
|
||||||
Settings,
|
Settings,
|
||||||
LogOut,
|
LogOut,
|
||||||
FileText,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
import { useAppTheme } from "@/components/ThemeProvider";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||||
@ -99,19 +98,6 @@ export function Header() {
|
|||||||
<Calendar className="w-4 h-4 sm:w-5 sm:h-5" />
|
<Calendar className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||||
<span className="hidden sm:inline">Book Appointment</span>
|
<span className="hidden sm:inline">Book Appointment</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
|
||||||
href="/deliverables"
|
|
||||||
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 === "/deliverables"
|
|
||||||
? "bg-linear-to-r from-rose-500 to-pink-600 text-white"
|
|
||||||
: isDark
|
|
||||||
? "text-gray-300 hover:bg-gray-800"
|
|
||||||
: "text-gray-600 hover:bg-gray-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<FileText className="w-4 h-4 sm:w-5 sm:h-5" />
|
|
||||||
<span className="hidden sm:inline">Documentation</span>
|
|
||||||
</Link>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Right Side Actions */}
|
{/* Right Side Actions */}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
X,
|
X,
|
||||||
Heart,
|
Heart,
|
||||||
FileText,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
import { useAppTheme } from "@/components/ThemeProvider";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
@ -21,7 +20,6 @@ import { toast } from "sonner";
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
{ label: "Dashboard", icon: LayoutGrid, href: "/admin/dashboard" },
|
{ label: "Dashboard", icon: LayoutGrid, href: "/admin/dashboard" },
|
||||||
{ label: "Book Appointment", icon: Calendar, href: "/admin/booking" },
|
{ label: "Book Appointment", icon: Calendar, href: "/admin/booking" },
|
||||||
{ label: "Deliverables", icon: FileText, href: "/deliverables" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function SideNav() {
|
export default function SideNav() {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -9,7 +8,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
UserCheck,
|
UserCheck,
|
||||||
@ -20,7 +18,6 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
ArrowDownRight,
|
ArrowDownRight,
|
||||||
FileText,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
import { useAppTheme } from "@/components/ThemeProvider";
|
||||||
import { getAllUsers } from "@/lib/actions/auth";
|
import { getAllUsers } from "@/lib/actions/auth";
|
||||||
@ -245,17 +242,7 @@ export default function Dashboard() {
|
|||||||
Here's an overview of your practice today
|
Here's an overview of your practice today
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<Select value={timePeriod} onValueChange={setTimePeriod}>
|
||||||
<Link href="/deliverables">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className={`flex items-center gap-2 ${isDark ? "bg-gray-800 border-gray-700 text-gray-100 hover:bg-gray-700" : "bg-white border-gray-200 text-gray-900 hover:bg-gray-50"}`}
|
|
||||||
>
|
|
||||||
<FileText className="w-4 h-4" />
|
|
||||||
<span className="hidden sm:inline">Deliverables</span>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Select value={timePeriod} onValueChange={setTimePeriod}>
|
|
||||||
<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"}`}>
|
<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" />
|
<SelectValue placeholder="Select period" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -265,7 +252,6 @@ export default function Dashboard() {
|
|||||||
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_year">Last Year</SelectItem>
|
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_year">Last Year</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
@ -375,14 +375,33 @@ function LoginContent() {
|
|||||||
{/* Heading */}
|
{/* 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">
|
<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">
|
||||||
{step === "login" && "Welcome back"}
|
{step === "login" && "Welcome back"}
|
||||||
|
{step === "signup" && "Create an account"}
|
||||||
{step === "verify" && "Verify your email"}
|
{step === "verify" && "Verify your email"}
|
||||||
</h1>
|
</h1>
|
||||||
{/* Subtitle */}
|
{/* Subtitle */}
|
||||||
{step === "login" && (
|
{step === "login" && (
|
||||||
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||||
Sign in to access your admin dashboard
|
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>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{step === "signup" && (
|
||||||
|
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||||
|
Already have an account?{" "}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setStep("login")}
|
||||||
|
className={`underline font-medium ${isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{step === "verify" && registeredEmail && (
|
{step === "verify" && registeredEmail && (
|
||||||
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||||
We've sent a verification code to <strong>{registeredEmail}</strong>
|
We've sent a verification code to <strong>{registeredEmail}</strong>
|
||||||
@ -500,6 +519,168 @@ function LoginContent() {
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Signup Form */}
|
||||||
|
{step === "signup" && (
|
||||||
|
<form className="space-y-4" onSubmit={handleSignup}>
|
||||||
|
{/* First Name Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="firstName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
First Name *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="firstName"
|
||||||
|
type="text"
|
||||||
|
placeholder="John"
|
||||||
|
value={signupData.first_name}
|
||||||
|
onChange={(e) => handleSignupChange("first_name", e.target.value)}
|
||||||
|
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'} ${errors.first_name ? 'border-red-500' : ''}`}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors.first_name && (
|
||||||
|
<p className="text-sm text-red-500">{errors.first_name}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Last Name Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="lastName" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
Last Name *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="lastName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Doe"
|
||||||
|
value={signupData.last_name}
|
||||||
|
onChange={(e) => handleSignupChange("last_name", e.target.value)}
|
||||||
|
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'} ${errors.last_name ? 'border-red-500' : ''}`}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors.last_name && (
|
||||||
|
<p className="text-sm text-red-500">{errors.last_name}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="signup-email" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
Email address *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="signup-email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email address"
|
||||||
|
value={signupData.email}
|
||||||
|
onChange={(e) => handleSignupChange("email", e.target.value)}
|
||||||
|
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'} ${errors.email ? 'border-red-500' : ''}`}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<p className="text-sm text-red-500">{errors.email}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Phone Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="phone" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
Phone Number (Optional)
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
type="tel"
|
||||||
|
placeholder="+1 (555) 123-4567"
|
||||||
|
value={signupData.phone_number || ""}
|
||||||
|
onChange={(e) => handleSignupChange("phone_number", e.target.value)}
|
||||||
|
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="signup-password" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
Password *
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="signup-password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="Password (min 8 characters)"
|
||||||
|
value={signupData.password}
|
||||||
|
onChange={(e) => handleSignupChange("password", e.target.value)}
|
||||||
|
className={`h-11 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'} ${errors.password ? 'border-red-500' : ''}`}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
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 ? (
|
||||||
|
<EyeOff className="w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<Eye className="w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{errors.password && (
|
||||||
|
<p className="text-sm text-red-500">{errors.password}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confirm Password Field */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="signup-password2" className={`text-sm font-medium ${isDark ? 'text-gray-300' : 'text-black'}`}>
|
||||||
|
Confirm Password *
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="signup-password2"
|
||||||
|
type={showPassword2 ? "text" : "password"}
|
||||||
|
placeholder="Confirm password"
|
||||||
|
value={signupData.password2}
|
||||||
|
onChange={(e) => handleSignupChange("password2", e.target.value)}
|
||||||
|
className={`h-11 pr-12 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'} ${errors.password2 ? 'border-red-500' : ''}`}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setShowPassword2(!showPassword2)}
|
||||||
|
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={showPassword2 ? "Hide password" : "Show password"}
|
||||||
|
>
|
||||||
|
{showPassword2 ? (
|
||||||
|
<EyeOff className="w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<Eye className="w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{errors.password2 && (
|
||||||
|
<p className="text-sm text-red-500">{errors.password2}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={registerMutation.isPending}
|
||||||
|
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 mt-6"
|
||||||
|
>
|
||||||
|
{registerMutation.isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Creating account...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Sign up"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* OTP Verification Form */}
|
{/* OTP Verification Form */}
|
||||||
{step === "verify" && (
|
{step === "verify" && (
|
||||||
@ -594,17 +775,17 @@ function LoginContent() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Back to login */}
|
{/* Back to signup */}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep("login");
|
setStep("signup");
|
||||||
setOtpData({ email: "", otp: "" });
|
setOtpData({ email: "", otp: "" });
|
||||||
}}
|
}}
|
||||||
className={`text-sm font-medium ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-600 hover:text-gray-700'}`}
|
className={`text-sm font-medium ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-600 hover:text-gray-700'}`}
|
||||||
>
|
>
|
||||||
← Back to login
|
← Back to signup
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,311 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ArrowLeft, Heart } from "lucide-react";
|
|
||||||
import React from "react";
|
|
||||||
import ReactMarkdown, { Components } from "react-markdown";
|
|
||||||
import remarkGfm from "remark-gfm";
|
|
||||||
|
|
||||||
const ReadmePage = () => {
|
|
||||||
const readmeContent = `
|
|
||||||
## Attune Heart Therapy
|
|
||||||
|
|
||||||
Welcome to your Attune Heart Therapy platform! This documentation provides everything you need to understand and navigate the complete system, including the landing page, booking system, user/client dashboard, and admin dashboard.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📂 What's Included
|
|
||||||
|
|
||||||
Your Attune Heart Therapy platform includes a comprehensive system for managing therapy appointments and client interactions:
|
|
||||||
|
|
||||||
| Section | Description |
|
|
||||||
| --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
||||||
| Landing Page | Public-facing homepage with navigation, services overview, and booking access |
|
|
||||||
| Booking System | User-friendly appointment booking flow where clients can request therapy sessions |
|
|
||||||
| User Dashboard | Client portal to view appointments, manage profile, and track booking status |
|
|
||||||
| Admin Dashboard | Administrative interface to manage appointments, view statistics, and schedule sessions |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Admin Dashboard Access
|
|
||||||
|
|
||||||
### Step 1: Navigate to Login
|
|
||||||
|
|
||||||
1. Go to your website's homepage
|
|
||||||
2. Click on the **"Admin Panel"** link in the footer (under Quick Links)
|
|
||||||
3. Or navigate directly to: \`https://attunehearttherapy.com/login\`
|
|
||||||
|
|
||||||
### Step 2: Login Credentials
|
|
||||||
|
|
||||||
**Email Address:** \`Hello@AttuneHeartTherapy.com\`
|
|
||||||
|
|
||||||
**Password:** \`G&n2S;ffTc8f\`
|
|
||||||
|
|
||||||
### Step 3: Access Dashboard
|
|
||||||
|
|
||||||
1. Enter your admin email address
|
|
||||||
2. Enter your password
|
|
||||||
3. Click **"Sign In"**
|
|
||||||
4. You will be automatically redirected to the Admin Dashboard
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Quick Access Links
|
|
||||||
|
|
||||||
[Visit Attune Heart Therapy](https://attunehearttherapy.com/) - Official website
|
|
||||||
|
|
||||||
[Access Admin Dashboard](https://attunehearttherapy.com/login) - Login to manage your practice
|
|
||||||
|
|
||||||
[Book an Appointment](https://attunehearttherapy.com/book-now) - Client booking page
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support & Contact
|
|
||||||
|
|
||||||
For technical assistance, questions, or issues:
|
|
||||||
|
|
||||||
**Email:** [info@BlackBusinessLabs.com](mailto:info@BlackBusinessLabs.com)
|
|
||||||
|
|
||||||
**Phone:** [(646) 895-4856](tel:+16468954856) - *CEO Tray Bailey's direct mobile*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*For questions or additional support, please contact Black Business Labs at the information provided above.*`;
|
|
||||||
|
|
||||||
const components: Components = {
|
|
||||||
h1: ({ node, ...props }) => (
|
|
||||||
<h1
|
|
||||||
style={{
|
|
||||||
fontSize: "2.2em",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: "1.2em",
|
|
||||||
marginBottom: "0.6em",
|
|
||||||
borderBottom: "1px solid #eaeaea",
|
|
||||||
paddingBottom: "0.3em",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
h2: ({ node, children, ...props }) => {
|
|
||||||
// Extract text content from children
|
|
||||||
const extractText = (child: any): string => {
|
|
||||||
if (typeof child === 'string') return child;
|
|
||||||
if (typeof child === 'number') return String(child);
|
|
||||||
if (React.isValidElement(child)) {
|
|
||||||
const childProps = child.props as any;
|
|
||||||
if (childProps?.children) {
|
|
||||||
return React.Children.toArray(childProps.children).map(extractText).join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const textContent = React.Children.toArray(children).map(extractText).join('');
|
|
||||||
|
|
||||||
// Check if this is the title heading
|
|
||||||
if (textContent.includes('Attune Heart Therapy - System Overview')) {
|
|
||||||
return (
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "1.8em",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: "1.2em",
|
|
||||||
marginBottom: "0.6em",
|
|
||||||
borderBottom: "1px solid #eaeaea",
|
|
||||||
paddingBottom: "0.3em",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.5em",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span>{children}</span>
|
|
||||||
</h2>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
fontSize: "1.8em",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: "1.2em",
|
|
||||||
marginBottom: "0.6em",
|
|
||||||
borderBottom: "1px solid #eaeaea",
|
|
||||||
paddingBottom: "0.3em",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</h2>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
h3: ({ node, ...props }) => (
|
|
||||||
<h3
|
|
||||||
style={{
|
|
||||||
fontSize: "1.5em",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: "1.2em",
|
|
||||||
marginBottom: "0.6em",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
p: ({ node, ...props }) => (
|
|
||||||
<p style={{ marginBottom: "1.2em", lineHeight: "1.8" }} {...props} />
|
|
||||||
),
|
|
||||||
a: ({ node, ...props }) => (
|
|
||||||
<a
|
|
||||||
style={{ color: "#0366d6", textDecoration: "none", fontWeight: "500" }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
ul: ({ node, ...props }) => (
|
|
||||||
<ul
|
|
||||||
style={{
|
|
||||||
paddingLeft: "1.5em",
|
|
||||||
marginBottom: "1.2em",
|
|
||||||
listStyleType: "disc",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
ol: ({ node, ...props }) => (
|
|
||||||
<ol
|
|
||||||
style={{
|
|
||||||
paddingLeft: "1.5em",
|
|
||||||
marginBottom: "1.2em",
|
|
||||||
listStyleType: "decimal",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
li: ({ node, ...props }) => (
|
|
||||||
<li style={{ marginBottom: "0.4em" }} {...props} />
|
|
||||||
),
|
|
||||||
table: ({ node, ...props }) => (
|
|
||||||
<table
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
borderCollapse: "collapse",
|
|
||||||
marginBottom: "1.2em",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
|
|
||||||
border: "1px solid #dfe2e5",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
th: ({ node, ...props }) => (
|
|
||||||
<th
|
|
||||||
style={{
|
|
||||||
border: "1px solid #dfe2e5",
|
|
||||||
padding: "0.6em 0.8em",
|
|
||||||
textAlign: "left",
|
|
||||||
backgroundColor: "#f6f8fa",
|
|
||||||
fontWeight: "600",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
td: ({ node, ...props }) => (
|
|
||||||
<td
|
|
||||||
style={{
|
|
||||||
border: "1px solid #dfe2e5",
|
|
||||||
padding: "0.6em 0.8em",
|
|
||||||
textAlign: "left",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
pre: ({ node, children, ...props }) => (
|
|
||||||
<pre
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#f6f8fa",
|
|
||||||
padding: "1em",
|
|
||||||
borderRadius: "6px",
|
|
||||||
overflowX: "auto",
|
|
||||||
fontSize: "0.9em",
|
|
||||||
lineHeight: "1.5",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</pre>
|
|
||||||
),
|
|
||||||
code: (props) => {
|
|
||||||
// Using `props: any` and casting to bypass TypeScript error with `inline` prop.
|
|
||||||
const {
|
|
||||||
node,
|
|
||||||
inline: isInline,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
// Destructure known non-HTML props from react-markdown to prevent them from being spread onto the <code> tag
|
|
||||||
index,
|
|
||||||
siblingCount,
|
|
||||||
ordered,
|
|
||||||
checked,
|
|
||||||
style: _style, // if style is passed in props, avoid conflict with style object below
|
|
||||||
...htmlProps // Spread remaining props, assuming they are valid HTML attributes for <code>
|
|
||||||
} = props as any;
|
|
||||||
|
|
||||||
const codeStyleBase = {
|
|
||||||
fontFamily:
|
|
||||||
'SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isInline) {
|
|
||||||
return (
|
|
||||||
<code
|
|
||||||
className={className}
|
|
||||||
style={{
|
|
||||||
...codeStyleBase,
|
|
||||||
backgroundColor: "rgba(27,31,35,0.07)", // Slightly adjusted for better visibility
|
|
||||||
padding: "0.2em 0.4em",
|
|
||||||
margin: "0 0.1em",
|
|
||||||
fontSize: "85%",
|
|
||||||
borderRadius: "3px",
|
|
||||||
}}
|
|
||||||
{...htmlProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For block code (inside <pre>)
|
|
||||||
return (
|
|
||||||
<code
|
|
||||||
className={className} // className might contain "language-js" etc.
|
|
||||||
style={{
|
|
||||||
...codeStyleBase,
|
|
||||||
// Most styling for block code is handled by the <pre> wrapper
|
|
||||||
// However, ensure no extra padding/margin if pre handles it
|
|
||||||
padding: 0,
|
|
||||||
backgroundColor: "transparent", // Pre has the background
|
|
||||||
}}
|
|
||||||
{...htmlProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
||||||
<div className="bg-white p-8 rounded-lg shadow-md">
|
|
||||||
<Button
|
|
||||||
className="bg-gray-100 hover:bg-gray-50 shadow-md text-black"
|
|
||||||
onClick={() => window.history.back()}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="mr-2" />
|
|
||||||
</Button>
|
|
||||||
<ReactMarkdown components={components} remarkPlugins={[remarkGfm]}>
|
|
||||||
{readmeContent}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ReadmePage;
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
export default function SwotAnalysisPage() {
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
|
|
||||||
// This ensures the PDF viewer only renders on the client side
|
|
||||||
useEffect(() => {
|
|
||||||
setIsClient(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<div className="mb-6">
|
|
||||||
<Link href="/docs">
|
|
||||||
<Button variant="outline" className="mb-4">
|
|
||||||
← Back to Documentation
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<h1 className="text-3xl font-bold mb-2">WODEY SWOT Analysis</h1>
|
|
||||||
<p className="text-gray-600 mb-6">
|
|
||||||
An in-depth analysis of Strengths, Weaknesses, Opportunities, and
|
|
||||||
Threats for the WODEY platform.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end mb-4">
|
|
||||||
<a
|
|
||||||
href="/docs/SWOT-Analysis.pdf"
|
|
||||||
download="WODEY-SWOT-Analysis.pdf"
|
|
||||||
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded shadow-md"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Download PDF
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isClient ? (
|
|
||||||
<div className="w-full h-[calc(100vh-200px)] rounded-lg overflow-hidden">
|
|
||||||
<Image
|
|
||||||
src="/WODEY-SWOT-Analysis.jpg"
|
|
||||||
alt="SWOT Analysis"
|
|
||||||
width={600}
|
|
||||||
height={600}
|
|
||||||
className="w-full h-full object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center w-full h-[calc(100vh-200px)] bg-gray-100 rounded-lg">
|
|
||||||
Loading viewer...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -55,7 +55,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
const validation = loginSchema.safeParse(loginData);
|
const validation = loginSchema.safeParse(loginData);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
const firstError = validation.error.issues[0];
|
const firstError = validation.error.issues[0];
|
||||||
toast.error(firstError.message);
|
setError(firstError.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,12 +64,13 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
|
|
||||||
if (result.tokens && result.user) {
|
if (result.tokens && result.user) {
|
||||||
toast.success("Login successful!");
|
toast.success("Login successful!");
|
||||||
setShowPassword(false);
|
setShowPassword(false);
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
onLoginSuccess();
|
onLoginSuccess();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : "Login failed. Please try again.";
|
const errorMessage = err instanceof Error ? err.message : "Login failed. Please try again.";
|
||||||
|
setError(errorMessage);
|
||||||
toast.error(errorMessage);
|
toast.error(errorMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,7 +83,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
const validation = registerSchema.safeParse(signupData);
|
const validation = registerSchema.safeParse(signupData);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
const firstError = validation.error.issues[0];
|
const firstError = validation.error.issues[0];
|
||||||
toast.error(firstError.message);
|
setError(firstError.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +106,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : "Signup failed. Please try again.";
|
const errorMessage = err instanceof Error ? err.message : "Signup failed. Please try again.";
|
||||||
|
setError(errorMessage);
|
||||||
toast.error(errorMessage);
|
toast.error(errorMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -158,9 +160,14 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
|
|
||||||
{/* Scrollable Content */}
|
{/* Scrollable Content */}
|
||||||
<div className="overflow-y-auto flex-1 px-6">
|
<div className="overflow-y-auto flex-1 px-6">
|
||||||
{/* Signup Form */}
|
{/* Signup Form */}
|
||||||
{isSignup ? (
|
{isSignup ? (
|
||||||
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleSignup}>
|
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleSignup}>
|
||||||
|
{error && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* First Name Field */}
|
{/* First Name Field */}
|
||||||
<div className="space-y-1.5 sm:space-y-2">
|
<div className="space-y-1.5 sm:space-y-2">
|
||||||
@ -176,7 +183,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
className={`h-11 sm:h-12 text-sm sm:text-base ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900'}`}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Last Name Field */}
|
{/* Last Name Field */}
|
||||||
<div className="space-y-1.5 sm:space-y-2">
|
<div className="space-y-1.5 sm:space-y-2">
|
||||||
@ -320,6 +327,11 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
|||||||
) : (
|
) : (
|
||||||
/* Login Form */
|
/* Login Form */
|
||||||
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleLogin}>
|
<form className="space-y-4 sm:space-y-5 py-4 sm:py-6" onSubmit={handleLogin}>
|
||||||
|
{error && (
|
||||||
|
<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 */}
|
{/* Email Field */}
|
||||||
<div className="space-y-1.5 sm:space-y-2">
|
<div className="space-y-1.5 sm:space-y-2">
|
||||||
|
|||||||
@ -29,15 +29,12 @@
|
|||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
|
||||||
"remark-gfm": "^4.0.1",
|
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/hast": "^3.0.4",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
877
pnpm-lock.yaml
877
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user