feat: frontend redesign and app structure refactor

This commit is contained in:
fullsizemalt 2025-11-19 20:09:48 -08:00
parent 527bda634b
commit 6d53ac2b72
30 changed files with 891 additions and 370 deletions

20
deploy_ui.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
# Deploy UI changes to Gemini VPS
VPS_IP="216.158.230.94"
# Assuming root or the user has SSH config for this IP.
# If you use a different user, please export VPS_USER="your-user" before running or edit this line.
VPS_USER="${VPS_USER:-root}"
REMOTE_PATH="/srv/containers/mtad-gemini"
echo "Deploying to $VPS_USER@$VPS_IP..."
echo "1. Syncing web directory..."
# Sync web folder, excluding heavy/unnecessary files
rsync -avz --exclude 'node_modules' --exclude '.next' --exclude '.git' ./web/ $VPS_USER@$VPS_IP:$REMOTE_PATH/web/
echo "2. Rebuilding frontend container..."
# Rebuild and restart the frontend service
ssh $VPS_USER@$VPS_IP "cd $REMOTE_PATH/backend && docker compose -f docker-compose.gemini.yml up -d --build frontend"
echo "Deployment complete! Please refresh your browser."

View file

@ -9,6 +9,10 @@ COPY package*.json ./
# Install dependencies # Install dependencies
RUN npm ci RUN npm ci
# Build arguments
ARG NEXT_PUBLIC_API_BASE_URL
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
# Copy source # Copy source
COPY . . COPY . .

View file

@ -0,0 +1,48 @@
import React from 'react'
export default function CommunityPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white font-heading">
Community
</h1>
<p className="mt-2 text-gray-600 dark:text-gray-400">
Connect with others, join support groups, and share your journey.
</p>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{/* Placeholder for community features */}
<div className="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
{/* Icon */}
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">
Support Groups
</dt>
<dd>
<div className="text-lg font-medium text-gray-900 dark:text-white">
Join a Group
</div>
</dd>
</dl>
</div>
</div>
</div>
<div className="bg-gray-50 dark:bg-gray-900 px-5 py-3">
<div className="text-sm">
<a href="/supportgroup" className="font-medium text-primary-600 hover:text-primary-500">
View all groups
</a>
</div>
</div>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,54 @@
'use client'
import { useAuth } from '@/lib/hooks/useAuth'
import { Link } from '@/components/common/Link'
export default function DashboardPage() {
const { user } = useAuth()
return (
<div className="space-y-6">
<div className="bg-white shadow rounded-lg p-6">
<h1 className="text-2xl font-bold text-gray-900">
Welcome back, {user?.display_name || user?.email?.split('@')[0] || 'Friend'}!
</h1>
<p className="mt-2 text-gray-600">
We're glad you're here. This is your personal space to connect, learn, and grow.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Quick Links */}
<div className="bg-white shadow rounded-lg p-6">
<h2 className="text-lg font-medium text-gray-900 mb-4">Community</h2>
<p className="text-gray-600 mb-4">
Join the conversation in our forums. Connect with others who understand your journey.
</p>
<Link href="https://forum.morethanadiagnosis.org" variant="primary">
Go to Forum
</Link>
</div>
<div className="bg-white shadow rounded-lg p-6">
<h2 className="text-lg font-medium text-gray-900 mb-4">Resources</h2>
<p className="text-gray-600 mb-4">
Explore our curated collection of resources to support you.
</p>
<Link href="/resources" variant="primary">
Browse Resources
</Link>
</div>
<div className="bg-white shadow rounded-lg p-6">
<h2 className="text-lg font-medium text-gray-900 mb-4">The Journal</h2>
<p className="text-gray-600 mb-4">
Read the latest stories and insights from our community blog.
</p>
<Link href="/thejournal" variant="primary">
Read Blog
</Link>
</div>
</div>
</div>
)
}

48
web/app/(app)/layout.tsx Normal file
View file

@ -0,0 +1,48 @@
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useAuth } from '@/lib/hooks/useAuth'
import { Sidebar } from '@/components/layout/Sidebar'
import { MobileNav } from '@/components/layout/MobileNav'
export default function AppLayout({
children,
}: {
children: React.ReactNode
}) {
const { isAuthenticated, isLoading } = useAuth()
const router = useRouter()
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.push('/login')
}
}, [isLoading, isAuthenticated, router])
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-600"></div>
</div>
)
}
if (!isAuthenticated) {
return null // Will redirect
}
return (
<div className="min-h-screen bg-gray-50">
<Sidebar />
<div className="md:pl-64 flex flex-col min-h-screen">
<main className="flex-1 pb-20 md:pb-0">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{children}
</div>
</main>
<MobileNav />
</div>
</div>
)
}

View file

@ -0,0 +1,108 @@
'use client';
import { useState, useEffect } from 'react';
interface PodcastEpisode {
id: string;
title: string;
description: string;
audio_url: string;
published_at: string;
duration: string;
image_url: string;
}
interface PodcastFeed {
title: string;
description: string;
image_url: string;
episodes: PodcastEpisode[];
}
const API_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://216.158.230.94:8001/api/v1';
export default function PodcastPage() {
const [feed, setFeed] = useState<PodcastFeed | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPodcasts = async () => {
try {
const response = await fetch(`${API_URL}/podcast/`);
if (!response.ok) {
throw new Error('Failed to fetch podcasts');
}
const data = await response.json();
setFeed(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
fetchPodcasts();
}, []);
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen bg-background">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
</div>
);
}
if (error) {
return (
<div className="flex justify-center items-center min-h-screen bg-background">
<div className="text-red-500 text-xl">Error: {error}</div>
</div>
);
}
return (
<div className="min-h-screen bg-background text-text p-8">
<div className="max-w-4xl mx-auto">
<header className="mb-12 text-center">
<h1 className="text-4xl font-heading font-bold text-primary mb-4">Podcasts</h1>
<p className="text-lg text-text opacity-80 font-body">Listen to our latest episodes and stories.</p>
</header>
<div className="space-y-8">
{feed?.episodes.map((episode) => (
<div key={episode.id} className="bg-surface rounded-xl shadow-card p-6 flex flex-col md:flex-row gap-6 transition-transform hover:scale-[1.01]">
<div className="flex-shrink-0">
<img
src={episode.image_url || feed.image_url}
alt={episode.title}
className="w-32 h-32 rounded-lg object-cover bg-secondary"
/>
</div>
<div className="flex-grow">
<h2 className="text-2xl font-heading font-semibold text-primary mb-2">{episode.title}</h2>
<div className="flex items-center gap-4 text-sm text-text opacity-60 mb-4 font-body">
<span>{new Date(episode.published_at).toLocaleDateString()}</span>
<span></span>
<span>{episode.duration}</span>
</div>
<div
className="text-text opacity-80 mb-6 font-body line-clamp-3"
dangerouslySetInnerHTML={{ __html: episode.description }}
/>
<audio
controls
className="w-full"
src={episode.audio_url}
>
Your browser does not support the audio element.
</audio>
</div>
</div>
))}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,75 @@
'use client'
import { useAuth } from '@/lib/hooks/useAuth'
import { Button } from '@/components/common/Button'
import { UserCircleIcon } from '@heroicons/react/24/solid'
export default function ProfilePage() {
const { user, logout } = useAuth()
if (!user) {
return null
}
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white font-heading">
My Profile
</h1>
<p className="mt-2 text-gray-600 dark:text-gray-400">
Manage your account settings and preferences.
</p>
</div>
<div className="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
<div className="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
User Information
</h3>
</div>
<div className="px-4 py-5 sm:p-6">
<div className="flex items-center mb-6">
<div className="h-20 w-20 rounded-full bg-primary-100 flex items-center justify-center text-primary-600">
<UserCircleIcon className="h-16 w-16" />
</div>
<div className="ml-6">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
{user.display_name || 'Community Member'}
</h2>
<p className="text-gray-500 dark:text-gray-400">{user.email}</p>
{user.is_verified && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 mt-2">
Verified Member
</span>
)}
</div>
</div>
<div className="border-t border-gray-200 dark:border-gray-700 pt-6">
<dl className="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">
User ID
</dt>
<dd className="mt-1 text-sm text-gray-900 dark:text-white font-mono">
{user.id}
</dd>
</div>
{/* Add more fields here as needed */}
</dl>
</div>
</div>
<div className="bg-gray-50 dark:bg-gray-900 px-4 py-4 sm:px-6 flex justify-end">
<Button
variant="secondary"
onClick={() => logout()}
className="text-red-600 hover:text-red-700 border-red-200 hover:border-red-300 hover:bg-red-50"
>
Sign Out
</Button>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,120 @@
'use client'
import Link from 'next/link'
import { useEffect } from 'react'
import { useApi } from '@/lib/hooks/useApi'
import { LoadingState } from '@/components/ui/LoadingState'
import { ErrorState } from '@/components/ui/ErrorState'
interface BlogPost {
id: string
title: string
link: string
published_at: string
summary: string
image_url?: string
author?: string
}
interface BlogResponse {
title: string
description: string
items: BlogPost[]
}
export default function TheJournalPage() {
const { data, error, isLoading, execute } = useApi<BlogResponse>()
useEffect(() => {
execute({
url: '/blog/rss',
method: 'GET',
})
}, [])
return (
<main className="min-h-screen bg-white">
<header className="sticky top-0 z-50 bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-gray-900">MoreThanADiagnosis</Link>
<nav className="hidden md:flex gap-8">
<Link href="/" className="text-gray-600 hover:text-blue-600">Home</Link>
<Link href="/thejournal" className="text-gray-600 hover:text-blue-600 font-semibold text-blue-600">The Journal</Link>
</nav>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{isLoading && <LoadingState message="Loading journal entries..." />}
{error && <ErrorState message="Failed to load journal entries." onRetry={() => window.location.reload()} />}
{!isLoading && !error && (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white font-heading">
The Journal
</h1>
<p className="mt-2 text-gray-600 dark:text-gray-400">
Reflections, stories, and updates from our community.
</p>
</div>
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{data?.items.map((post) => (
<article
key={post.id}
className="flex flex-col bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300"
>
{post.image_url && (
<div className="flex-shrink-0 h-48 w-full relative">
<img
className="h-full w-full object-cover"
src={post.image_url}
alt={post.title}
/>
</div>
)}
<div className="flex-1 p-6 flex flex-col justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-primary-600">
{post.author || 'MoreThanADiagnosis'}
</p>
<a href={post.link} target="_blank" rel="noopener noreferrer" className="block mt-2">
<p className="text-xl font-semibold text-gray-900 dark:text-white">
{post.title}
</p>
<div
className="mt-3 text-base text-gray-500 dark:text-gray-400 line-clamp-3"
dangerouslySetInnerHTML={{ __html: post.summary || '' }}
/>
</a>
</div>
<div className="mt-6 flex items-center">
<div className="flex-shrink-0">
<span className="sr-only">{post.published_at}</span>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
<time dateTime={post.published_at}>
{new Date(post.published_at).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
</div>
</div>
</article>
))}
</div>
</div>
)}
</div>
<footer className="bg-gray-900 text-white py-12 border-t border-gray-800">
<div className="max-w-7xl mx-auto px-4 text-center">
<p>&copy; 2025 More Than A Diagnosis. All rights reserved.</p>
</div>
</footer>
</main>
)
}

View file

@ -139,7 +139,7 @@ export default function LoginPage() {
<div className="text-center text-sm text-gray-600 dark:text-gray-400"> <div className="text-center text-sm text-gray-600 dark:text-gray-400">
Don't have an account?{' '} Don't have an account?{' '}
<Link href="/auth/signup" variant="primary"> <Link href="/signup" variant="primary">
Sign up Sign up
</Link> </Link>
</div> </div>

View file

@ -77,7 +77,7 @@ export default function ResetPasswordPage() {
</p> </p>
<div className="pt-4"> <div className="pt-4">
<Link href="/auth/login" variant="primary"> <Link href="/login" variant="primary">
<Button variant="ghost" fullWidth> <Button variant="ghost" fullWidth>
Back to login Back to login
</Button> </Button>
@ -134,7 +134,7 @@ export default function ResetPasswordPage() {
<div className="text-center text-sm text-gray-600 dark:text-gray-400"> <div className="text-center text-sm text-gray-600 dark:text-gray-400">
Remember your password?{' '} Remember your password?{' '}
<Link href="/auth/login" variant="primary"> <Link href="/login" variant="primary">
Sign in Sign in
</Link> </Link>
</div> </div>

View file

@ -173,7 +173,7 @@ export default function SignupPage() {
<div className="text-center text-sm text-gray-600 dark:text-gray-400"> <div className="text-center text-sm text-gray-600 dark:text-gray-400">
Already have an account?{' '} Already have an account?{' '}
<Link href="/auth/login" variant="primary"> <Link href="/login" variant="primary">
Sign in Sign in
</Link> </Link>
</div> </div>

View file

@ -3,30 +3,35 @@
@tailwind utilities; @tailwind utilities;
:root { :root {
/* Light mode colors */ /* Compassionate Theme Colors */
--color-primary: #3B82F6; --color-primary: #5D7B6F; /* Sage */
--color-secondary: #8B5CF6; --color-secondary: #E6DACE; /* Sand */
--color-accent: #D4A373; /* Muted Coral */
--color-error: #EF4444; --color-error: #EF4444;
--color-success: #10B981; --color-success: #10B981;
--color-warning: #F59E0B; --color-warning: #F59E0B;
--color-bg: #FFFFFF; --color-bg: #FAF9F6; /* Cream */
--color-bg-secondary: #F9FAFB; --color-bg-secondary: #FFFFFF; /* White */
--color-text: #111827; --color-text: #2C3333; /* Charcoal */
--color-text-secondary: #6B7280; --color-text-secondary: #6B7280; /* Muted Text */
--color-border: #D1D5DB; --color-border: #E5E7EB;
/* Spacing base unit */ /* Spacing base unit */
--spacing-unit: 4px; --spacing-unit: 4px;
} }
.dark { .dark {
/* Dark mode colors */ /* Dark mode colors - adapted for softness */
--color-bg: #1F2937; --color-bg: #1A1D1D; /* Dark Charcoal */
--color-bg-secondary: #111827; --color-bg-secondary: #2C3333;
--color-text: #F9FAFB; --color-text: #FAF9F6; /* Cream Text */
--color-text-secondary: #D1D5DB; --color-text-secondary: #9CA3AF;
--color-border: #4B5563; --color-border: #374151;
--color-primary: #749688; /* Lighter Sage */
--color-secondary: #5D4E40; /* Darker Sand */
} }
* { * {

View file

@ -1,6 +1,19 @@
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { Lora, Source_Sans_3 } from 'next/font/google'
import './globals.css' import './globals.css'
const lora = Lora({
subsets: ['latin'],
variable: '--font-lora',
display: 'swap',
})
const sourceSans = Source_Sans_3({
subsets: ['latin'],
variable: '--font-source-sans',
display: 'swap',
})
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'MoreThanADiagnosis', title: 'MoreThanADiagnosis',
description: 'Community platform for health advocacy and support', description: 'Community platform for health advocacy and support',
@ -12,8 +25,8 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="en"> <html lang="en" className={`${lora.variable} ${sourceSans.variable}`}>
<body className="font-sans antialiased">{children}</body> <body className="font-sans antialiased bg-background text-text">{children}</body>
</html> </html>
) )
} }

View file

@ -4,96 +4,113 @@ import Link from 'next/link'
export default function Home() { export default function Home() {
return ( return (
<main className="min-h-screen bg-white"> <main className="min-h-screen bg-background text-text font-sans selection:bg-primary-200 selection:text-primary-900">
{/* Header */} {/* Header */}
<header className="sticky top-0 z-50 bg-white shadow-sm border-b border-gray-200"> <header className="sticky top-0 z-50 bg-background/80 backdrop-blur-md border-b border-primary-100">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-gray-900"> <Link href="/" className="text-2xl font-heading font-bold text-primary-700 hover:text-primary-600 transition-colors">
MoreThanADiagnosis MoreThanADiagnosis
</Link> </Link>
<nav className="hidden md:flex gap-8"> <nav className="hidden md:flex gap-8">
<Link href="/" className="text-gray-600 hover:text-blue-600 transition-colors">Home</Link> <Link href="/" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Home</Link>
<Link href="/podcast" className="text-gray-600 hover:text-blue-600 transition-colors">Podcast</Link> <Link href="/podcast" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Podcast</Link>
<Link href="/resources" className="text-gray-600 hover:text-blue-600 transition-colors">Resources</Link> <Link href="/resources" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Resources</Link>
<Link href="/happymail" className="text-gray-600 hover:text-blue-600 transition-colors">Happy Mail</Link> <Link href="/happymail" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Happy Mail</Link>
<Link href="/supportgroup" className="text-gray-600 hover:text-blue-600 transition-colors">Support Group</Link> <Link href="/supportgroup" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Support Group</Link>
<Link href="/shop" className="text-gray-600 hover:text-blue-600 transition-colors">Shop</Link> <Link href="/shop" className="text-text-muted hover:text-primary-600 transition-colors font-medium">Shop</Link>
</nav> </nav>
</div> </div>
</div> </div>
</header> </header>
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20"> <section className="relative overflow-hidden py-24 lg:py-32">
<div className="absolute inset-0 bg-gradient-to-br from-primary-50 via-background to-secondary-50 opacity-70 -z-10" />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center"> <div className="text-center max-w-4xl mx-auto">
<h1 className="text-6xl md:text-7xl font-bold text-gray-900 mb-6"> <h1 className="text-5xl md:text-7xl font-heading font-bold text-primary-800 mb-8 leading-tight">
You are more than a diagnosis. You are more than <br className="hidden md:block" /> a diagnosis.
</h1> </h1>
<h2 className="text-2xl md:text-3xl text-gray-700 mb-8"> <h2 className="text-2xl md:text-3xl text-text-muted mb-10 font-light leading-relaxed">
Connecting Through Stories, Thriving Through Community Connecting Through Stories, <span className="text-accent font-medium">Thriving Through Community</span>
</h2> </h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-12"> <p className="text-xl text-text-muted max-w-2xl mx-auto mb-12 leading-relaxed">
More Than A Diagnosis is a place for connection, encouragement, support, and resources by and for folks living with chronic illness and those touched by cancer. Join us on a journey where we explore the human experience beyond medical labels. Discover inspiring stories, insightful conversations, and valuable resources that redefine what it means to live with a diagnosis. A sanctuary for connection, encouragement, and support. Join us on a journey where we explore the human experience beyond medical labels.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link <Link
href="/supportgroup" href="/supportgroup"
className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors shadow-lg" className="inline-flex items-center justify-center px-8 py-4 bg-primary text-white font-semibold rounded-full hover:bg-primary-600 transition-all shadow-lg hover:shadow-xl hover:-translate-y-0.5"
> >
Join Our Community Join Our Community
</Link> </Link>
<Link
href="/resources"
className="inline-flex items-center justify-center px-8 py-4 bg-white text-primary-700 font-semibold rounded-full border-2 border-primary-100 hover:border-primary-300 hover:bg-primary-50 transition-all"
>
Explore Resources
</Link>
</div>
</div> </div>
</div> </div>
</section> </section>
{/* Happy Mail Section */} {/* Happy Mail Section */}
<section className="py-20 bg-white border-t border-gray-200"> <section className="py-24 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center"> <div className="grid md:grid-cols-2 gap-16 items-center">
<div> <div className="order-2 md:order-1">
<h3 className="text-4xl font-bold text-gray-900 mb-6">Happy Mail</h3> <div className="bg-secondary-50 rounded-3xl p-10 md:p-14 relative overflow-hidden">
<p className="text-xl text-gray-600 mb-4"> <div className="absolute top-0 right-0 w-64 h-64 bg-secondary-100 rounded-full -mr-32 -mt-32 opacity-50" />
Happy Mail is a small way to remind you: you're seen, supported, and not alone. <div className="absolute bottom-0 left-0 w-48 h-48 bg-secondary-200 rounded-full -ml-24 -mb-24 opacity-50" />
</p> <div className="relative z-10 text-center">
<p className="text-lg text-gray-600 mb-6"> <div className="text-8xl mb-6 animate-bounce-slow">💌</div>
Nerisa sends free, joy-filled snail mail to folks navigating the hard stuff - just because. <p className="text-secondary-800 font-heading font-bold text-xl">From the Worst Club's Best Members</p>
</div>
</div>
</div>
<div className="order-1 md:order-2">
<span className="text-accent font-bold tracking-wider uppercase text-sm mb-2 block">Spreading Joy</span>
<h3 className="text-4xl font-heading font-bold text-primary-800 mb-6">Happy Mail</h3>
<p className="text-xl text-text-muted mb-6 leading-relaxed">
Happy Mail is a small way to remind you: you're seen, supported, and not alone. Nerisa sends free, joy-filled snail mail to folks navigating the hard stuff - just because.
</p> </p>
<div className="mb-8"> <div className="mb-10 bg-primary-50 rounded-2xl p-6 border border-primary-100">
<h4 className="text-lg font-semibold text-gray-900 mb-4">Who Can Receive Happy Mail?</h4> <h4 className="text-lg font-bold text-primary-800 mb-4">Who Can Receive Happy Mail?</h4>
<ul className="space-y-2 text-gray-700"> <ul className="space-y-3 text-text-muted">
<li> Cancer diagnosis or treatment</li> {['Cancer diagnosis or treatment', 'Chronic illness or rare disease', 'Medical limbo or recovery'].map((item, i) => (
<li> Chronic illness or rare disease</li> <li key={i} className="flex items-center gap-3">
<li> Medical limbo or recovery</li> <span className="flex-shrink-0 w-6 h-6 rounded-full bg-success text-white flex items-center justify-center text-sm"></span>
{item}
</li>
))}
</ul> </ul>
</div> </div>
<Link <Link
href="/happymail" href="/happymail"
className="inline-block px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors" className="inline-flex items-center px-6 py-3 bg-secondary text-secondary-900 font-semibold rounded-full hover:bg-secondary-400 transition-colors"
> >
Order Happy Mail Order Happy Mail
</Link> </Link>
</div> </div>
<div className="bg-gradient-to-br from-pink-100 to-purple-100 rounded-lg p-8 text-center">
<div className="text-6xl mb-4">💌</div>
<p className="text-gray-700 font-semibold">From the Worst Club's Best Members</p>
</div>
</div> </div>
</div> </div>
</section> </section>
{/* Connect Section */} {/* Connect Section */}
<section className="py-20 bg-gradient-to-br from-blue-600 to-purple-600 text-white"> <section className="py-24 bg-primary-700 text-white relative overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div className="absolute inset-0 bg-[url('/pattern.svg')] opacity-5" />
<h3 className="text-4xl font-bold mb-8">Connect</h3> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center relative z-10">
<blockquote className="text-2xl mb-8 italic"> <h3 className="text-4xl font-heading font-bold mb-10">Connect</h3>
<blockquote className="text-2xl md:text-3xl font-serif italic mb-12 leading-relaxed opacity-90">
"We're here to create a safe, supportive space where you can connect with others, share your story, and find hope. Cancer and chronic illness can feel so isolating, but together, we're stronger." "We're here to create a safe, supportive space where you can connect with others, share your story, and find hope. Cancer and chronic illness can feel so isolating, but together, we're stronger."
</blockquote> </blockquote>
<Link <Link
href="/supportgroup" href="/supportgroup"
className="inline-block px-8 py-4 bg-white text-blue-600 font-semibold rounded-lg hover:bg-gray-100 transition-colors" className="inline-block px-10 py-4 bg-white text-primary-800 font-bold rounded-full hover:bg-primary-50 transition-all shadow-lg hover:scale-105"
> >
Learn More Learn More
</Link> </Link>
@ -101,26 +118,27 @@ export default function Home() {
</section> </section>
{/* Podcast Section */} {/* Podcast Section */}
<section className="py-20 bg-white border-t border-gray-200"> <section className="py-24 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h3 className="text-4xl font-bold text-gray-900 mb-12 text-center">Podcast</h3> <div className="text-center mb-16">
<div className="grid md:grid-cols-2 gap-12 items-center"> <h3 className="text-4xl font-heading font-bold text-primary-800 mb-4">The Podcast</h3>
<div className="bg-gradient-to-br from-orange-100 to-pink-100 rounded-lg p-8 text-center"> <p className="text-text-muted max-w-2xl mx-auto">Real conversations about life beyond the medical chart.</p>
<div className="text-6xl mb-4">🎙</div> </div>
<p className="text-gray-700 font-semibold">More Than A Diagnosis Podcast</p>
<div className="grid md:grid-cols-2 gap-12 items-center bg-white rounded-3xl p-8 md:p-12 shadow-sm border border-primary-50">
<div className="bg-gradient-to-br from-accent/20 to-secondary/20 rounded-2xl p-12 text-center aspect-square flex items-center justify-center">
<div className="text-8xl">🎙</div>
</div> </div>
<div> <div>
<p className="text-lg text-gray-600 mb-6"> <h4 className="text-2xl font-heading font-bold text-primary-800 mb-6">More Than A Diagnosis</h4>
Listen to More Than A Diagnosis where we get real about life beyond the medical chart. <p className="text-text-muted mb-8 leading-relaxed">
</p> Hosts Jes and Den are lifelong friends who found their way back to each other thanks to the wild ride of cancer and chronic illness. Jes went through triple-negative breast cancer and now navigates the long-term side effects of treatment. Den lives with FAP, a rare genetic condition. Through it all, they've found the magic of community.
<p className="text-gray-700 mb-8">
Hosts Jes and Den are lifelong friends who found their way back to each other thanks to the wild ride of cancer and chronic illness. Jes went through triple-negative breast cancer and now navigates the long-term side effects of treatment. Den lives with FAP, a rare genetic condition, alongside other chronic illnesses. Through it all, they've found the magic of community, connection, and telling the real, messy stories.
</p> </p>
<Link <Link
href="/podcast" href="/podcast"
className="inline-block px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors" className="inline-flex items-center px-8 py-3 bg-primary text-white font-semibold rounded-full hover:bg-primary-600 transition-colors"
> >
Listen Here Listen Now
</Link> </Link>
</div> </div>
</div> </div>
@ -128,106 +146,107 @@ export default function Home() {
</section> </section>
{/* Resources Section */} {/* Resources Section */}
<section className="py-20 bg-gray-50 border-t border-gray-200"> <section className="py-24 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h3 className="text-4xl font-bold text-gray-900 mb-8 text-center">Resources</h3> <h3 className="text-4xl font-heading font-bold text-primary-800 mb-8">Resources</h3>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-12 text-center"> <p className="text-xl text-text-muted max-w-3xl mx-auto mb-12 leading-relaxed">
We know how scary and overwhelming it can be to receive a diagnosis, not to mention the financial burden it can bring. That's why we've put together a list of helpful resources to attempt to make this journey even just a little bit easier for you. Be sure to check back often or sign up to receive updates as we are adding new resources all the time! We know how scary and overwhelming it can be to receive a diagnosis. We've put together a list of helpful resources to attempt to make this journey even just a little bit easier for you.
</p> </p>
<div className="text-center">
<Link <Link
href="/resources" href="/resources"
className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors" className="inline-block px-8 py-4 bg-secondary text-secondary-900 font-semibold rounded-full hover:bg-secondary-400 transition-colors"
> >
View Resources Browse Resources
</Link> </Link>
</div> </div>
</div>
</section> </section>
{/* Wings of Remembrance Section */} {/* Wings of Remembrance Section */}
<section className="py-20 bg-white border-t border-gray-200"> <section className="py-24 bg-primary-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h3 className="text-4xl font-bold text-gray-900 mb-8 text-center">Wings of Remembrance</h3> <span className="text-accent font-bold tracking-wider uppercase text-sm mb-2 block">Honoring Legacies</span>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-12 text-center"> <h3 className="text-4xl font-heading font-bold text-primary-800 mb-8">Wings of Remembrance</h3>
<p className="text-xl text-text-muted max-w-3xl mx-auto mb-12 leading-relaxed">
We invite you to share a tributea memory, message, or reflectionto honor those who shaped our journey. Together, we create a tapestry of wings that celebrates their legacy. We invite you to share a tributea memory, message, or reflectionto honor those who shaped our journey. Together, we create a tapestry of wings that celebrates their legacy.
</p> </p>
<div className="text-center">
<Link <Link
href="/inlovingmemory" href="/inlovingmemory"
className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors" className="inline-block px-8 py-4 bg-white text-primary-700 font-semibold rounded-full border border-primary-200 hover:bg-primary-50 transition-colors shadow-sm"
> >
Share Your Tribute Share Your Tribute
</Link> </Link>
</div> </div>
</div>
</section> </section>
{/* Shop Section */} {/* Shop Section */}
<section className="py-20 bg-gray-50 border-t border-gray-200"> <section className="py-24 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h3 className="text-4xl font-bold text-gray-900 mb-12 text-center">Shop Our Collections</h3> <div className="text-center mb-16">
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12"> <h3 className="text-4xl font-heading font-bold text-primary-800 mb-4">Shop Our Collections</h3>
<p className="text-text-muted">Wear your story with pride.</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
{[ {[
{ name: 'Worst Club Best Members', icon: '🦆' }, { name: 'Worst Club Best Members', icon: '🦆', color: 'bg-primary-50' },
{ name: 'More Than A Diagnosis', icon: '💪' }, { name: 'More Than A Diagnosis', icon: '💪', color: 'bg-secondary-50' },
{ name: 'I Don\'t Want To I Get To', icon: '✨' }, { name: 'I Don\'t Want To I Get To', icon: '✨', color: 'bg-accent/10' },
{ name: 'Ribbon Collection', icon: '🎗️' } { name: 'Ribbon Collection', icon: '🎗️', color: 'bg-primary-100' }
].map((collection) => ( ].map((collection) => (
<div key={collection.name} className="bg-white rounded-lg p-6 text-center hover:shadow-lg transition-shadow"> <div key={collection.name} className={`${collection.color} rounded-2xl p-8 text-center hover:shadow-md transition-all hover:-translate-y-1 cursor-pointer group`}>
<div className="text-4xl mb-4">{collection.icon}</div> <div className="text-5xl mb-6 group-hover:scale-110 transition-transform duration-300">{collection.icon}</div>
<h4 className="text-lg font-semibold text-gray-900">{collection.name}</h4> <h4 className="text-lg font-bold text-primary-900">{collection.name}</h4>
</div> </div>
))} ))}
</div> </div>
<div className="text-center"> <div className="text-center">
<Link <Link
href="/shop" href="/shop"
className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors" className="inline-block px-8 py-4 bg-primary text-white font-semibold rounded-full hover:bg-primary-600 transition-colors"
> >
Shop Now Visit Shop
</Link> </Link>
</div> </div>
</div> </div>
</section> </section>
{/* Footer */} {/* Footer */}
<footer className="bg-gray-900 text-white py-12 border-t border-gray-800"> <footer className="bg-primary-900 text-white py-16 border-t border-primary-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-4 gap-8 mb-8"> <div className="grid md:grid-cols-4 gap-12 mb-12">
<div> <div>
<h4 className="font-bold mb-4">Navigation</h4> <h4 className="font-heading font-bold text-lg mb-6 text-primary-100">Navigation</h4>
<ul className="space-y-2"> <ul className="space-y-3">
<li><Link href="/" className="hover:text-blue-400">Home</Link></li> <li><Link href="/" className="text-primary-200 hover:text-white transition-colors">Home</Link></li>
<li><Link href="/podcast" className="hover:text-blue-400">Podcast</Link></li> <li><Link href="/podcast" className="text-primary-200 hover:text-white transition-colors">Podcast</Link></li>
<li><Link href="/resources" className="hover:text-blue-400">Resources</Link></li> <li><Link href="/resources" className="text-primary-200 hover:text-white transition-colors">Resources</Link></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 className="font-bold mb-4">Community</h4> <h4 className="font-heading font-bold text-lg mb-6 text-primary-100">Community</h4>
<ul className="space-y-2"> <ul className="space-y-3">
<li><Link href="/supportgroup" className="hover:text-blue-400">Support Group</Link></li> <li><Link href="/supportgroup" className="text-primary-200 hover:text-white transition-colors">Support Group</Link></li>
<li><Link href="/groups" className="hover:text-blue-400">Support Circle</Link></li> <li><Link href="/groups" className="text-primary-200 hover:text-white transition-colors">Support Circle</Link></li>
<li><Link href="/thejournal" className="hover:text-blue-400">The Journal</Link></li> <li><Link href="/thejournal" className="text-primary-200 hover:text-white transition-colors">The Journal</Link></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 className="font-bold mb-4">More</h4> <h4 className="font-heading font-bold text-lg mb-6 text-primary-100">More</h4>
<ul className="space-y-2"> <ul className="space-y-3">
<li><Link href="/happymail" className="hover:text-blue-400">Happy Mail</Link></li> <li><Link href="/happymail" className="text-primary-200 hover:text-white transition-colors">Happy Mail</Link></li>
<li><Link href="/inlovingmemory" className="hover:text-blue-400">In Loving Memory</Link></li> <li><Link href="/inlovingmemory" className="text-primary-200 hover:text-white transition-colors">In Loving Memory</Link></li>
<li><Link href="/shop" className="hover:text-blue-400">Shop</Link></li> <li><Link href="/shop" className="text-primary-200 hover:text-white transition-colors">Shop</Link></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 className="font-bold mb-4">Connect</h4> <h4 className="font-heading font-bold text-lg mb-6 text-primary-100">Connect</h4>
<ul className="space-y-2"> <ul className="space-y-3">
<li><Link href="/meetus" className="hover:text-blue-400">Meet Us</Link></li> <li><Link href="/meetus" className="text-primary-200 hover:text-white transition-colors">Meet Us</Link></li>
<li><a href="https://www.morethanadiagnosis.org" className="hover:text-blue-400">Original Site</a></li> <li><a href="https://www.morethanadiagnosis.org" className="text-primary-200 hover:text-white transition-colors">Original Site</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="border-t border-gray-800 pt-8 text-center text-gray-400"> <div className="border-t border-primary-800 pt-8 text-center text-primary-400">
<p>&copy; 2025 More Than A Diagnosis. All rights reserved.</p> <p>&copy; 2025 More Than A Diagnosis. All rights reserved.</p>
</div> </div>
</div> </div>

View file

@ -1,96 +0,0 @@
'use client'
import Link from 'next/link'
export default function PodcastPage() {
return (
<main className="min-h-screen bg-white">
{/* Header */}
<header className="sticky top-0 z-50 bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-gray-900">
MoreThanADiagnosis
</Link>
<nav className="hidden md:flex gap-8">
<Link href="/" className="text-gray-600 hover:text-blue-600 transition-colors">Home</Link>
<Link href="/podcast" className="text-gray-600 hover:text-blue-600 transition-colors font-semibold text-blue-600">Podcast</Link>
<Link href="/resources" className="text-gray-600 hover:text-blue-600 transition-colors">Resources</Link>
<Link href="/happymail" className="text-gray-600 hover:text-blue-600 transition-colors">Happy Mail</Link>
<Link href="/supportgroup" className="text-gray-600 hover:text-blue-600 transition-colors">Support Group</Link>
<Link href="/shop" className="text-gray-600 hover:text-blue-600 transition-colors">Shop</Link>
</nav>
</div>
</div>
</header>
{/* Hero */}
<section className="bg-gradient-to-br from-orange-50 to-pink-50 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<div className="text-6xl mb-6">🎙</div>
<h1 className="text-5xl font-bold text-gray-900 mb-6">More Than A Diagnosis Podcast</h1>
<p className="text-2xl text-gray-700">
Getting real about life beyond the medical chart
</p>
</div>
</section>
{/* Hosts */}
<section className="py-20 bg-white border-t border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-4xl font-bold text-gray-900 mb-12 text-center">Meet Your Hosts</h2>
<div className="grid md:grid-cols-2 gap-12">
<div className="text-center">
<div className="bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg p-12 mb-6">
<div className="text-6xl">👩</div>
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">Jes</h3>
<p className="text-gray-700 text-lg">
Jes went through triple-negative breast cancer and now navigates the long-term side effects of treatment. She's passionate about sharing real, messy stories of survival and resilience.
</p>
</div>
<div className="text-center">
<div className="bg-gradient-to-br from-green-100 to-teal-100 rounded-lg p-12 mb-6">
<div className="text-6xl">👨</div>
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">Den</h3>
<p className="text-gray-700 text-lg">
Den lives with FAP, a rare genetic condition, alongside other chronic illnesses. Together with Jes, he creates space for authentic conversations about community and connection.
</p>
</div>
</div>
<div className="mt-12 text-center">
<p className="text-xl text-gray-700 max-w-3xl mx-auto">
Jes and Den are lifelong friends who found their way back to each other thanks to the wild ride of cancer and chronic illness. Through it all, they've found the magic of community, connection, and telling the real, messy stories.
</p>
</div>
</div>
</section>
{/* Episodes */}
<section className="py-20 bg-gray-50 border-t border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-4xl font-bold text-gray-900 mb-12 text-center">Listen Now</h2>
<p className="text-xl text-gray-700 text-center max-w-3xl mx-auto mb-12">
Available on all major podcast platforms. Subscribe to never miss an episode where we get real about life beyond the medical chart.
</p>
<div className="text-center">
<a
href="#"
className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors"
>
Find on Podcast Apps
</a>
</div>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-900 text-white py-12 border-t border-gray-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<p>&copy; 2025 More Than A Diagnosis. All rights reserved.</p>
</div>
</footer>
</main>
)
}

View file

@ -1,37 +0,0 @@
'use client'
import Link from 'next/link'
export default function JournalPage() {
return (
<main className="min-h-screen bg-white">
<header className="sticky top-0 z-50 bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-gray-900">MoreThanADiagnosis</Link>
<nav className="hidden md:flex gap-8">
<Link href="/" className="text-gray-600 hover:text-blue-600">Home</Link>
<Link href="/thejournal" className="text-gray-600 hover:text-blue-600 font-semibold text-blue-600">The Journal</Link>
</nav>
</div>
</div>
</header>
<section className="bg-gradient-to-br from-yellow-50 to-orange-50 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<div className="text-6xl mb-6">📝</div>
<h1 className="text-5xl font-bold text-gray-900 mb-6">The Journal</h1>
<p className="text-2xl text-gray-700">Community Stories & Reflections</p>
</div>
</section>
<section className="py-20 bg-white border-t border-gray-200">
<div className="max-w-3xl mx-auto px-4">
<p className="text-xl text-gray-700 mb-8">Read inspiring stories from our community members as they navigate their journeys with chronic illness and cancer. Share your own story and inspire others.</p>
<a href="https://www.morethanadiagnosis.org/thejournal" className="inline-block px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700">Read Stories</a>
</div>
</section>
<footer className="bg-gray-900 text-white py-12 border-t border-gray-800">
<div className="max-w-7xl mx-auto px-4 text-center">
<p>&copy; 2025 More Than A Diagnosis. All rights reserved.</p>
</div>
</footer>
</main>
)
}

View file

@ -81,7 +81,7 @@ export const Header = ({
<Button variant="ghost" size="sm" onClick={onLogin}> <Button variant="ghost" size="sm" onClick={onLogin}>
Login Login
</Button> </Button>
<Link href="/auth/signup"> <Link href="/signup">
<Button variant="primary" size="sm"> <Button variant="primary" size="sm">
Sign Up Sign Up
</Button> </Button>
@ -175,7 +175,7 @@ export const Header = ({
</button> </button>
<div onClick={() => setMobileMenuOpen(false)}> <div onClick={() => setMobileMenuOpen(false)}>
<Link <Link
href="/auth/signup" href="/signup"
variant="neutral" variant="neutral"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md no-underline" className="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md no-underline"
> >

View file

@ -0,0 +1,46 @@
'use client'
import { Link } from '../common/Link'
import { usePathname } from 'next/navigation'
import {
HomeIcon,
BookOpenIcon,
UserGroupIcon,
UserIcon
} from '@heroicons/react/24/outline'
const navigation = [
{ name: 'Home', href: '/', icon: HomeIcon },
{ name: 'Journal', href: '/thejournal', icon: BookOpenIcon },
{ name: 'Forum', href: 'https://forum.morethanadiagnosis.org', icon: UserGroupIcon },
{ name: 'Profile', href: '/profile', icon: UserIcon },
]
export function MobileNav() {
const pathname = usePathname()
return (
<div className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 pb-safe">
<div className="flex justify-around items-center h-16">
{navigation.map((item) => {
const isActive = pathname === item.href
return (
<Link
key={item.name}
href={item.href}
variant="neutral"
className={`flex flex-col items-center justify-center w-full h-full space-y-1 ${isActive ? 'text-primary-600' : 'text-gray-500 hover:text-gray-900'
}`}
>
<item.icon
className={`h-6 w-6 ${isActive ? 'text-primary-600' : 'text-gray-400'}`}
aria-hidden="true"
/>
<span className="text-xs font-medium">{item.name}</span>
</Link>
)
})}
</div>
</div>
)
}

View file

@ -0,0 +1,77 @@
'use client'
import { Link } from '../common/Link'
import { usePathname } from 'next/navigation'
import {
HomeIcon,
MicrophoneIcon,
BookOpenIcon,
UserGroupIcon,
UserIcon,
ArrowRightOnRectangleIcon
} from '@heroicons/react/24/outline'
import { useAuth } from '@/lib/hooks/useAuth'
const navigation = [
{ name: 'Home', href: '/dashboard', icon: HomeIcon },
{ name: 'Podcasts', href: '/podcast', icon: MicrophoneIcon },
{ name: 'The Journal', href: '/thejournal', icon: BookOpenIcon },
{ name: 'Forum', href: 'https://forum.morethanadiagnosis.org', icon: UserGroupIcon },
{ name: 'Resources', href: '/resources', icon: BookOpenIcon },
{ name: 'Profile', href: '/profile', icon: UserIcon },
]
export function Sidebar() {
const pathname = usePathname()
const { logout } = useAuth()
return (
<div className="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0">
<div className="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-white">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4 mb-8">
<span className="text-xl font-bold text-primary-600">MoreThanADiagnosis</span>
</div>
<nav className="mt-5 flex-1 px-2 space-y-1">
{navigation.map((item) => {
const isActive = pathname === item.href
return (
<Link
key={item.name}
href={item.href}
variant="neutral"
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md transition-colors ${isActive
? 'bg-primary-50 text-primary-700'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}`}
>
<item.icon
className={`mr-3 flex-shrink-0 h-6 w-6 ${isActive ? 'text-primary-600' : 'text-gray-400 group-hover:text-gray-500'
}`}
aria-hidden="true"
/>
{item.name}
</Link>
)
})}
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<button
onClick={() => logout()}
className="flex-shrink-0 w-full group block"
>
<div className="flex items-center">
<ArrowRightOnRectangleIcon className="inline-block h-5 w-5 text-gray-400 group-hover:text-gray-500" />
<div className="ml-3">
<p className="text-sm font-medium text-gray-700 group-hover:text-gray-900">
Sign out
</p>
</div>
</div>
</button>
</div>
</div>
</div>
)
}

View file

@ -19,12 +19,12 @@ export const DashboardLayout = ({ children }: DashboardLayoutProps) => {
// Redirect to login if not authenticated // Redirect to login if not authenticated
React.useEffect(() => { React.useEffect(() => {
if (!isAuthenticated) { if (!isAuthenticated) {
router.push('/auth/login') router.push('/login')
} }
}, [isAuthenticated, router]) }, [isAuthenticated, router])
const handleLogin = () => { const handleLogin = () => {
router.push('/auth/login') router.push('/login')
} }
const sidebarItems = [ const sidebarItems = [
@ -62,8 +62,7 @@ export const DashboardLayout = ({ children }: DashboardLayoutProps) => {
variant="neutral" variant="neutral"
className={` className={`
flex items-center px-4 py-2 text-sm font-medium rounded-md no-underline flex items-center px-4 py-2 text-sm font-medium rounded-md no-underline
${ ${isActive
isActive
? 'bg-primary-100 text-primary-900 dark:bg-primary-900 dark:text-primary-100' ? 'bg-primary-100 text-primary-900 dark:bg-primary-900 dark:text-primary-100'
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
} }
@ -112,8 +111,7 @@ export const DashboardLayout = ({ children }: DashboardLayoutProps) => {
variant="neutral" variant="neutral"
className={` className={`
flex items-center px-4 py-2 text-sm font-medium rounded-md no-underline flex items-center px-4 py-2 text-sm font-medium rounded-md no-underline
${ ${isActive
isActive
? 'bg-primary-100 text-primary-900 dark:bg-primary-900 dark:text-primary-100' ? 'bg-primary-100 text-primary-900 dark:bg-primary-900 dark:text-primary-100'
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
} }

View file

@ -15,7 +15,7 @@ export const MainLayout = ({ children }: MainLayoutProps) => {
const router = useRouter() const router = useRouter()
const handleLogin = () => { const handleLogin = () => {
router.push('/auth/login') router.push('/login')
} }
return ( return (

View file

@ -0,0 +1,23 @@
import React from 'react'
import { Button } from '../common/Button'
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
interface ErrorStateProps {
message?: string
onRetry?: () => void
}
export function ErrorState({ message = 'Something went wrong.', onRetry }: ErrorStateProps) {
return (
<div className="flex flex-col items-center justify-center py-12 text-center">
<ExclamationTriangleIcon className="h-12 w-12 text-red-500 mb-4" />
<p className="text-gray-900 dark:text-white font-medium mb-2">Error</p>
<p className="text-gray-500 dark:text-gray-400 mb-6 max-w-md">{message}</p>
{onRetry && (
<Button onClick={onRetry} variant="primary">
Try Again
</Button>
)}
</div>
)
}

View file

@ -0,0 +1,14 @@
import React from 'react'
interface LoadingStateProps {
message?: string
}
export function LoadingState({ message = 'Loading...' }: LoadingStateProps) {
return (
<div className="flex flex-col items-center justify-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-600 mb-4"></div>
<p className="text-gray-500 dark:text-gray-400">{message}</p>
</div>
)
}

View file

@ -63,7 +63,7 @@ apiClient.interceptors.response.use(
localStorage.removeItem('refresh_token') localStorage.removeItem('refresh_token')
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.location.href = '/auth/login' window.location.href = '/login'
} }
return Promise.reject(refreshError) return Promise.reject(refreshError)

View file

@ -1,10 +1,8 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'export',
reactStrictMode: true, reactStrictMode: true,
swcMinify: true, swcMinify: true,
images: { images: {
unoptimized: true, // Required for static export
remotePatterns: [ remotePatterns: [
{ {
protocol: 'https', protocol: 'https',

10
web/package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "morethanadiagnosis-web", "name": "morethanadiagnosis-web",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@heroicons/react": "^2.2.0",
"@tanstack/react-query": "^5.25.0", "@tanstack/react-query": "^5.25.0",
"axios": "^1.6.0", "axios": "^1.6.0",
"next": "^14.0.0", "next": "^14.0.0",
@ -163,6 +164,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@heroicons/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
"license": "MIT",
"peerDependencies": {
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",

View file

@ -12,14 +12,15 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\"" "format": "prettier --write \"**/*.{ts,tsx,md}\""
}, },
"dependencies": { "dependencies": {
"react": "^18.2.0", "@heroicons/react": "^2.2.0",
"react-dom": "^18.2.0",
"next": "^14.0.0",
"@tanstack/react-query": "^5.25.0", "@tanstack/react-query": "^5.25.0",
"axios": "^1.6.0", "axios": "^1.6.0",
"zustand": "^4.4.0", "next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.3.0", "tailwindcss": "^3.3.0",
"typescript": "^5.3.0" "typescript": "^5.3.0",
"zustand": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.10.0", "@types/node": "^20.10.0",
@ -27,10 +28,10 @@
"@types/react-dom": "^18.2.0", "@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.13.0", "@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.13.0", "@typescript-eslint/parser": "^6.13.0",
"autoprefixer": "^10.4.16",
"eslint": "^8.54.0", "eslint": "^8.54.0",
"eslint-config-next": "^14.0.0", "eslint-config-next": "^14.0.0",
"prettier": "^3.1.0", "postcss": "^8.4.31",
"autoprefixer": "^10.4.16", "prettier": "^3.1.0"
"postcss": "^8.4.31"
} }
} }

6
web/postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View file

@ -11,97 +11,64 @@ const config: Config = {
extend: { extend: {
colors: { colors: {
primary: { primary: {
DEFAULT: '#3B82F6', DEFAULT: '#0F4C5C', // Deep Teal (Authoritative)
50: '#EFF6FF', 50: '#F0F6F7',
100: '#DBEAFE', 100: '#E1EDF0',
200: '#BFDBFE', 200: '#C3DBE1',
300: '#93C5FD', 300: '#A5C9D2',
400: '#60A5FA', 400: '#87B7C3',
500: '#3B82F6', 500: '#69A5B4',
600: '#2563EB', 600: '#4B93A5',
700: '#1D4ED8', 700: '#2D8196',
800: '#1E40AF', 800: '#0F4C5C', // Main
900: '#1E3A8A', 900: '#0C3D4A',
}, },
secondary: { secondary: {
DEFAULT: '#8B5CF6', DEFAULT: '#E6DACE', // Warm Sand (Tender)
50: '#F5F3FF', 50: '#FCFBF9',
100: '#EDE9FE', 100: '#F7F4F1',
200: '#DDD6FE', 200: '#EFE9E3',
300: '#C4B5FD', 300: '#E6DACE',
400: '#A78BFA', 400: '#D6C4B3',
500: '#8B5CF6', 500: '#C6AE98',
600: '#7C3AED', 600: '#B6987D',
700: '#6D28D9', 700: '#A68262',
800: '#5B21B6', 800: '#8C6C4E',
900: '#4C1D95', 900: '#72563A',
},
background: {
DEFAULT: '#F7F9F9', // Soft White (Professional)
paper: '#FFFFFF',
},
text: {
DEFAULT: '#334155', // Dark Slate (Readable)
muted: '#64748B',
},
accent: {
DEFAULT: '#E07A5F', // Soft Coral (Compassionate)
}, },
error: { error: {
DEFAULT: '#EF4444', DEFAULT: '#EF4444',
50: '#FEF2F2',
100: '#FEE2E2',
200: '#FECACA',
300: '#FCA5A5',
400: '#F87171',
500: '#EF4444',
600: '#DC2626',
700: '#B91C1C',
800: '#991B1B',
900: '#7F1D1D',
}, },
success: { success: {
DEFAULT: '#10B981', DEFAULT: '#10B981',
50: '#ECFDF5',
100: '#D1FAE5',
200: '#A7F3D0',
300: '#6EE7B7',
400: '#34D399',
500: '#10B981',
600: '#059669',
700: '#047857',
800: '#065F46',
900: '#064E3B',
}, },
warning: { warning: {
DEFAULT: '#F59E0B', DEFAULT: '#F59E0B',
50: '#FFFBEB',
100: '#FEF3C7',
200: '#FDE68A',
300: '#FCD34D',
400: '#FBBF24',
500: '#F59E0B',
600: '#D97706',
700: '#B45309',
800: '#92400E',
900: '#78350F',
}, },
}, },
fontFamily: { fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'], sans: ['var(--font-source-sans)', 'system-ui', 'sans-serif'],
}, serif: ['var(--font-lora)', 'Georgia', 'serif'],
fontSize: { heading: ['var(--font-lora)', 'Georgia', 'serif'],
xs: ['12px', { lineHeight: '1.5' }],
sm: ['14px', { lineHeight: '1.5' }],
base: ['16px', { lineHeight: '1.5' }],
lg: ['18px', { lineHeight: '1.5' }],
xl: ['20px', { lineHeight: '1.5' }],
'2xl': ['24px', { lineHeight: '1.25' }],
'3xl': ['32px', { lineHeight: '1.25' }],
'4xl': ['48px', { lineHeight: '1.25' }],
}, },
borderRadius: { borderRadius: {
sm: '4px', sm: '8px',
DEFAULT: '8px', DEFAULT: '12px',
md: '8px', md: '16px',
lg: '12px', lg: '24px',
full: '9999px', full: '9999px',
}, },
boxShadow: {
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
DEFAULT: '0 4px 6px rgba(0, 0, 0, 0.1)',
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px rgba(0, 0, 0, 0.15)',
},
}, },
}, },
plugins: [], plugins: [],