diff --git a/deploy_ui.sh b/deploy_ui.sh new file mode 100755 index 0000000..c6a2d4f --- /dev/null +++ b/deploy_ui.sh @@ -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." diff --git a/web/Dockerfile b/web/Dockerfile index 1fc68d8..50d30d0 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -9,6 +9,10 @@ COPY package*.json ./ # Install dependencies 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 . . diff --git a/web/app/(app)/community/page.tsx b/web/app/(app)/community/page.tsx new file mode 100644 index 0000000..e44f3dc --- /dev/null +++ b/web/app/(app)/community/page.tsx @@ -0,0 +1,48 @@ +import React from 'react' + +export default function CommunityPage() { + return ( +
+
+

+ Community +

+

+ Connect with others, join support groups, and share your journey. +

+
+ +
+ {/* Placeholder for community features */} +
+
+
+
+ {/* Icon */} +
+
+
+
+ Support Groups +
+
+
+ Join a Group +
+
+
+
+
+
+
+
+ + View all groups + +
+
+
+
+
+ ) +} diff --git a/web/app/(app)/dashboard/page.tsx b/web/app/(app)/dashboard/page.tsx new file mode 100644 index 0000000..ed8353e --- /dev/null +++ b/web/app/(app)/dashboard/page.tsx @@ -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 ( +
+
+

+ Welcome back, {user?.display_name || user?.email?.split('@')[0] || 'Friend'}! +

+

+ We're glad you're here. This is your personal space to connect, learn, and grow. +

+
+ +
+ {/* Quick Links */} +
+

Community

+

+ Join the conversation in our forums. Connect with others who understand your journey. +

+ + Go to Forum + +
+ +
+

Resources

+

+ Explore our curated collection of resources to support you. +

+ + Browse Resources + +
+ +
+

The Journal

+

+ Read the latest stories and insights from our community blog. +

+ + Read Blog + +
+
+
+ ) +} diff --git a/web/app/(app)/layout.tsx b/web/app/(app)/layout.tsx new file mode 100644 index 0000000..3136f8e --- /dev/null +++ b/web/app/(app)/layout.tsx @@ -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 ( +
+
+
+ ) + } + + if (!isAuthenticated) { + return null // Will redirect + } + + return ( +
+ +
+
+
+ {children} +
+
+ +
+
+ ) +} diff --git a/web/app/(app)/podcast/page.tsx b/web/app/(app)/podcast/page.tsx new file mode 100644 index 0000000..7b8e6b1 --- /dev/null +++ b/web/app/(app)/podcast/page.tsx @@ -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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( +
+
+
+ ); + } + + if (error) { + return ( +
+
Error: {error}
+
+ ); + } + + return ( +
+
+
+

Podcasts

+

Listen to our latest episodes and stories.

+
+ +
+ {feed?.episodes.map((episode) => ( +
+
+ {episode.title} +
+
+

{episode.title}

+
+ {new Date(episode.published_at).toLocaleDateString()} + β€’ + {episode.duration} +
+
+ + +
+
+ ))} +
+
+
+ ); +} diff --git a/web/app/(app)/profile/page.tsx b/web/app/(app)/profile/page.tsx new file mode 100644 index 0000000..9bf994b --- /dev/null +++ b/web/app/(app)/profile/page.tsx @@ -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 ( +
+
+

+ My Profile +

+

+ Manage your account settings and preferences. +

+
+ +
+
+

+ User Information +

+
+
+
+
+ +
+
+

+ {user.display_name || 'Community Member'} +

+

{user.email}

+ {user.is_verified && ( + + Verified Member + + )} +
+
+ +
+
+
+
+ User ID +
+
+ {user.id} +
+
+ {/* Add more fields here as needed */} +
+
+
+
+ +
+
+
+ ) +} diff --git a/web/app/resources/page.tsx b/web/app/(app)/resources/page.tsx similarity index 100% rename from web/app/resources/page.tsx rename to web/app/(app)/resources/page.tsx diff --git a/web/app/(app)/thejournal/page.tsx b/web/app/(app)/thejournal/page.tsx new file mode 100644 index 0000000..5070961 --- /dev/null +++ b/web/app/(app)/thejournal/page.tsx @@ -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() + + useEffect(() => { + execute({ + url: '/blog/rss', + method: 'GET', + }) + }, []) + + return ( +
+
+
+
+ MoreThanADiagnosis + +
+
+
+
+ {isLoading && } + + {error && window.location.reload()} />} + + {!isLoading && !error && ( +
+
+

+ The Journal +

+

+ Reflections, stories, and updates from our community. +

+
+ +
+ {data?.items.map((post) => ( +
+ {post.image_url && ( +
+ {post.title} +
+ )} +
+
+

+ {post.author || 'MoreThanADiagnosis'} +

+ +

+ {post.title} +

+
+ +
+
+
+ {post.published_at} +
+
+ +
+
+
+
+ ))} +
+
+ )} +
+
+
+

© 2025 More Than A Diagnosis. All rights reserved.

+
+
+
+ ) +} diff --git a/web/app/(auth)/login/page.tsx b/web/app/(auth)/login/page.tsx index 49162cf..199bc75 100644 --- a/web/app/(auth)/login/page.tsx +++ b/web/app/(auth)/login/page.tsx @@ -139,7 +139,7 @@ export default function LoginPage() {
Don't have an account?{' '} - + Sign up
diff --git a/web/app/(auth)/reset-password/page.tsx b/web/app/(auth)/reset-password/page.tsx index 938dd80..425aa95 100644 --- a/web/app/(auth)/reset-password/page.tsx +++ b/web/app/(auth)/reset-password/page.tsx @@ -77,7 +77,7 @@ export default function ResetPasswordPage() {

- + @@ -134,7 +134,7 @@ export default function ResetPasswordPage() {
Remember your password?{' '} - + Sign in
diff --git a/web/app/(auth)/signup/page.tsx b/web/app/(auth)/signup/page.tsx index 0ae2f4f..292b56b 100644 --- a/web/app/(auth)/signup/page.tsx +++ b/web/app/(auth)/signup/page.tsx @@ -173,7 +173,7 @@ export default function SignupPage() {
Already have an account?{' '} - + Sign in
diff --git a/web/app/globals.css b/web/app/globals.css index 1996a6e..f7ba447 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -3,30 +3,35 @@ @tailwind utilities; :root { - /* Light mode colors */ - --color-primary: #3B82F6; - --color-secondary: #8B5CF6; + /* Compassionate Theme Colors */ + --color-primary: #5D7B6F; /* Sage */ + --color-secondary: #E6DACE; /* Sand */ + --color-accent: #D4A373; /* Muted Coral */ + --color-error: #EF4444; --color-success: #10B981; --color-warning: #F59E0B; - --color-bg: #FFFFFF; - --color-bg-secondary: #F9FAFB; - --color-text: #111827; - --color-text-secondary: #6B7280; - --color-border: #D1D5DB; + --color-bg: #FAF9F6; /* Cream */ + --color-bg-secondary: #FFFFFF; /* White */ + --color-text: #2C3333; /* Charcoal */ + --color-text-secondary: #6B7280; /* Muted Text */ + --color-border: #E5E7EB; /* Spacing base unit */ --spacing-unit: 4px; } .dark { - /* Dark mode colors */ - --color-bg: #1F2937; - --color-bg-secondary: #111827; - --color-text: #F9FAFB; - --color-text-secondary: #D1D5DB; - --color-border: #4B5563; + /* Dark mode colors - adapted for softness */ + --color-bg: #1A1D1D; /* Dark Charcoal */ + --color-bg-secondary: #2C3333; + --color-text: #FAF9F6; /* Cream Text */ + --color-text-secondary: #9CA3AF; + --color-border: #374151; + + --color-primary: #749688; /* Lighter Sage */ + --color-secondary: #5D4E40; /* Darker Sand */ } * { diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 9ed4c6a..203f145 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,6 +1,19 @@ import type { Metadata } from 'next' +import { Lora, Source_Sans_3 } from 'next/font/google' 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 = { title: 'MoreThanADiagnosis', description: 'Community platform for health advocacy and support', @@ -12,8 +25,8 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - - {children} + + {children} ) } diff --git a/web/app/page.tsx b/web/app/page.tsx index 001376d..5abaaa4 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -4,96 +4,113 @@ import Link from 'next/link' export default function Home() { return ( -
+
{/* Header */} -
+
- + MoreThanADiagnosis
{/* Hero Section */} -
+
+
-
-

- You are more than a diagnosis. +
+

+ You are more than
a diagnosis.

-

- Connecting Through Stories, Thriving Through Community +

+ Connecting Through Stories, Thriving Through Community

-

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

- - Join Our Community - +
+ + Join Our Community + + + Explore Resources + +

{/* Happy Mail Section */} -
+
-
-
-

Happy Mail

-

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

+
+
+
+
+
+
πŸ’Œ
+

From the Worst Club's Best Members

+
+
+
+
+ Spreading Joy +

Happy Mail

+

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

-
-

Who Can Receive Happy Mail?

-
    -
  • βœ“ Cancer diagnosis or treatment
  • -
  • βœ“ Chronic illness or rare disease
  • -
  • βœ“ Medical limbo or recovery
  • +
    +

    Who Can Receive Happy Mail?

    +
      + {['Cancer diagnosis or treatment', 'Chronic illness or rare disease', 'Medical limbo or recovery'].map((item, i) => ( +
    • + βœ“ + {item} +
    • + ))}
    Order Happy Mail
-
-
πŸ’Œ
-

From the Worst Club's Best Members

-
{/* Connect Section */} -
-
-

Connect

-
+
+
+
+

Connect

+
"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."
Learn More @@ -101,26 +118,27 @@ export default function Home() {
{/* Podcast Section */} -
+
-

Podcast

-
-
-
πŸŽ™οΈ
-

More Than A Diagnosis Podcast

+
+

The Podcast

+

Real conversations about life beyond the medical chart.

+
+ +
+
+
πŸŽ™οΈ
-

- Listen to More Than A Diagnosis where we get real about life beyond the medical chart. -

-

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

More Than A Diagnosis

+

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

- Listen Here + Listen Now
@@ -128,106 +146,107 @@ export default function Home() {
{/* Resources Section */} -
-
-

Resources

-

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

+
+

Resources

+

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

-
- - View Resources - -
+ + Browse Resources +
{/* Wings of Remembrance Section */} -
-
-

Wings of Remembrance

-

+

+
+ Honoring Legacies +

Wings of Remembrance

+

We invite you to share a tributeβ€”a memory, message, or reflectionβ€”to honor those who shaped our journey. Together, we create a tapestry of wings that celebrates their legacy.

-
- - Share Your Tribute - -
+ + Share Your Tribute +
{/* Shop Section */} -
+
-

Shop Our Collections

-
+
+

Shop Our Collections

+

Wear your story with pride.

+
+ +
{[ - { name: 'Worst Club Best Members', icon: 'πŸ¦†' }, - { name: 'More Than A Diagnosis', icon: 'πŸ’ͺ' }, - { name: 'I Don\'t Want To I Get To', icon: '✨' }, - { name: 'Ribbon Collection', icon: 'πŸŽ—οΈ' } + { name: 'Worst Club Best Members', icon: 'πŸ¦†', color: 'bg-primary-50' }, + { name: 'More Than A Diagnosis', icon: 'πŸ’ͺ', color: 'bg-secondary-50' }, + { name: 'I Don\'t Want To I Get To', icon: '✨', color: 'bg-accent/10' }, + { name: 'Ribbon Collection', icon: 'πŸŽ—οΈ', color: 'bg-primary-100' } ].map((collection) => ( -
-
{collection.icon}
-

{collection.name}

+
+
{collection.icon}
+

{collection.name}

))}
- Shop Now + Visit Shop
{/* Footer */} -