feat/deliverables #22
@ -14,6 +14,7 @@ import {
|
||||
Bell,
|
||||
Settings,
|
||||
LogOut,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
@ -98,6 +99,19 @@ export function Header() {
|
||||
<Calendar className="w-4 h-4 sm:w-5 sm:h-5" />
|
||||
<span className="hidden sm:inline">Book Appointment</span>
|
||||
</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>
|
||||
|
||||
{/* Right Side Actions */}
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
Menu,
|
||||
X,
|
||||
Heart,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
@ -20,6 +21,7 @@ import { toast } from "sonner";
|
||||
const navItems = [
|
||||
{ label: "Dashboard", icon: LayoutGrid, href: "/admin/dashboard" },
|
||||
{ label: "Book Appointment", icon: Calendar, href: "/admin/booking" },
|
||||
{ label: "Deliverables", icon: FileText, href: "/deliverables" },
|
||||
];
|
||||
|
||||
export default function SideNav() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -8,6 +9,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Users,
|
||||
UserCheck,
|
||||
@ -18,6 +20,7 @@ import {
|
||||
TrendingUp,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
import { useAppTheme } from "@/components/ThemeProvider";
|
||||
import { getAllUsers } from "@/lib/actions/auth";
|
||||
@ -242,7 +245,17 @@ export default function Dashboard() {
|
||||
Here's an overview of your practice today
|
||||
</p>
|
||||
</div>
|
||||
<Select value={timePeriod} onValueChange={setTimePeriod}>
|
||||
<div className="flex items-center gap-3">
|
||||
<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"}`}>
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
@ -252,6 +265,7 @@ export default function Dashboard() {
|
||||
<SelectItem className={isDark ? "focus:bg-gray-700" : ""} value="last_year">Last Year</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
|
||||
@ -375,33 +375,14 @@ function LoginContent() {
|
||||
{/* 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">
|
||||
{step === "login" && "Welcome back"}
|
||||
{step === "signup" && "Create an account"}
|
||||
{step === "verify" && "Verify your email"}
|
||||
</h1>
|
||||
{/* Subtitle */}
|
||||
{step === "login" && (
|
||||
<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>
|
||||
Sign in to access your admin dashboard
|
||||
</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 && (
|
||||
<p className={`mb-6 ${isDark ? 'text-gray-400' : 'text-gray-600'}`}>
|
||||
We've sent a verification code to <strong>{registeredEmail}</strong>
|
||||
@ -519,168 +500,6 @@ function LoginContent() {
|
||||
</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 */}
|
||||
{step === "verify" && (
|
||||
@ -775,17 +594,17 @@ function LoginContent() {
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Back to signup */}
|
||||
{/* Back to login */}
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStep("signup");
|
||||
setStep("login");
|
||||
setOtpData({ email: "", otp: "" });
|
||||
}}
|
||||
className={`text-sm font-medium ${isDark ? 'text-gray-400 hover:text-gray-300' : 'text-gray-600 hover:text-gray-700'}`}
|
||||
>
|
||||
← Back to signup
|
||||
← Back to login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
311
app/(pages)/deliverables/page.tsx
Normal file
311
app/(pages)/deliverables/page.tsx
Normal file
@ -0,0 +1,311 @@
|
||||
"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;
|
||||
72
app/(pages)/deliverables/swot/page.tsx
Normal file
72
app/(pages)/deliverables/swot/page.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
"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);
|
||||
if (!validation.success) {
|
||||
const firstError = validation.error.issues[0];
|
||||
setError(firstError.message);
|
||||
toast.error(firstError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -64,13 +64,12 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
|
||||
if (result.tokens && result.user) {
|
||||
toast.success("Login successful!");
|
||||
setShowPassword(false);
|
||||
onOpenChange(false);
|
||||
onLoginSuccess();
|
||||
setShowPassword(false);
|
||||
onOpenChange(false);
|
||||
onLoginSuccess();
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : "Login failed. Please try again.";
|
||||
setError(errorMessage);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
@ -83,7 +82,7 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
const validation = registerSchema.safeParse(signupData);
|
||||
if (!validation.success) {
|
||||
const firstError = validation.error.issues[0];
|
||||
setError(firstError.message);
|
||||
toast.error(firstError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,7 +105,6 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : "Signup failed. Please try again.";
|
||||
setError(errorMessage);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
@ -160,14 +158,9 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
|
||||
{/* Scrollable Content */}
|
||||
<div className="overflow-y-auto flex-1 px-6">
|
||||
{/* Signup Form */}
|
||||
{isSignup ? (
|
||||
{/* Signup Form */}
|
||||
{isSignup ? (
|
||||
<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 */}
|
||||
<div className="space-y-1.5 sm:space-y-2">
|
||||
@ -183,7 +176,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'}`}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Last Name Field */}
|
||||
<div className="space-y-1.5 sm:space-y-2">
|
||||
@ -327,11 +320,6 @@ export function LoginDialog({ open, onOpenChange, onLoginSuccess }: LoginDialogP
|
||||
) : (
|
||||
/* Login Form */
|
||||
<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 */}
|
||||
<div className="space-y-1.5 sm:space-y-2">
|
||||
|
||||
@ -29,12 +29,15 @@
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^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