Files
my-loves-calendar/components/CosmicPulseModal.tsx
drjones c373bbdab4 first commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 21:05:27 -08:00

174 lines
8.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, Activity, Heart, AlertTriangle, Zap, MessageCircle, Moon, Cloud } from 'lucide-react';
import { AppSettings, CalendarEvent, PulseAnalysis } from '../types';
import { analyzeRelationshipPulse, interpretDream } from '../services/aiService';
import { Button } from './Button';
interface CosmicPulseModalProps {
isOpen: boolean;
onClose: () => void;
events: CalendarEvent[];
settings: AppSettings;
}
export const CosmicPulseModal: React.FC<CosmicPulseModalProps> = ({ isOpen, onClose, events, settings }) => {
const [analysis, setAnalysis] = useState<PulseAnalysis | null>(null);
const [loading, setLoading] = useState(false);
const [dreamText, setDreamText] = useState('');
const [dreamResult, setDreamResult] = useState('');
const [dreamLoading, setDreamLoading] = useState(false);
useEffect(() => {
if (isOpen) {
setLoading(true);
analyzeRelationshipPulse(events, settings).then(res => {
setAnalysis(res);
setLoading(false);
});
} else {
// Reset state when closed
setAnalysis(null);
setDreamResult('');
setDreamText('');
}
}, [isOpen, events, settings]);
const handleInterpretDream = async () => {
if (!dreamText) return;
setDreamLoading(true);
const res = await interpretDream(dreamText, settings);
setDreamResult(res);
setDreamLoading(false);
};
return (
<AnimatePresence>
{isOpen && (
<>
<motion.div
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/80 backdrop-blur-md z-50"
onClick={onClose}
/>
<motion.div
initial={{ scale: 0.8, opacity: 0, y: 50 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.8, opacity: 0, y: 50 }}
className="fixed inset-0 flex items-center justify-center z-50 pointer-events-none p-4"
>
<div className="glass-panel w-full max-w-lg rounded-3xl overflow-hidden pointer-events-auto flex flex-col max-h-[90vh] border border-pink-500/30 shadow-[0_0_60px_rgba(236,72,153,0.3)]">
{/* Header */}
<div className="p-6 border-b border-white/10 flex justify-between items-center bg-black/40">
<h2 className="text-2xl font-magical text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-purple-400 flex items-center gap-3">
<Activity className="animate-pulse text-pink-500" /> Cosmic Pulse
</h2>
<button onClick={onClose} className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400">
<X size={24} />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 space-y-6 custom-scrollbar">
{loading ? (
<div className="flex flex-col items-center justify-center py-12 gap-4">
<div className="relative">
<div className="w-16 h-16 border-4 border-pink-500/30 border-t-pink-500 rounded-full animate-spin"></div>
<div className="absolute inset-0 flex items-center justify-center">
<Activity size={24} className="text-pink-500 animate-pulse" />
</div>
</div>
<p className="text-pink-300 font-magical animate-pulse">Analyzing Relationship Rythm...</p>
<p className="text-xs text-gray-500">Using {settings.aiProvider === 'ollama' ? 'Local Spirit (Ollama)' : 'Cloud Mind (Gemini)'}</p>
</div>
) : analysis ? (
<>
{/* Harmony Score */}
<div className="bg-gradient-to-br from-purple-900/50 to-pink-900/50 p-6 rounded-3xl border border-pink-500/20 text-center relative overflow-hidden">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')] opacity-20"></div>
<p className="text-pink-200 text-sm font-bold uppercase tracking-widest mb-2">Harmony Score</p>
<div className="text-6xl font-black text-white drop-shadow-[0_0_15px_rgba(236,72,153,0.5)]">
{analysis.harmonyScore}
</div>
<div className="mt-2 inline-block px-4 py-1 rounded-full bg-white/10 text-sm font-bold text-pink-200 border border-white/10">
Current Vibe: {analysis.vibe}
</div>
</div>
{/* Alerts */}
{analysis.burnoutWarning && (
<motion.div initial={{ x: -20, opacity: 0 }} animate={{ x: 0, opacity: 1 }} className="bg-red-900/30 border border-red-500/30 p-4 rounded-2xl flex items-start gap-3">
<AlertTriangle className="text-red-400 shrink-0" />
<div>
<h4 className="font-bold text-red-200 text-sm">Burnout Warning</h4>
<p className="text-xs text-red-300/80 mt-1">{analysis.burnoutWarning}</p>
</div>
</motion.div>
)}
{/* Conflicts */}
{analysis.upcomingConflicts.length > 0 && (
<div className="space-y-2">
<h3 className="text-sm font-bold text-gray-400 uppercase tracking-wider flex items-center gap-2">
<Zap size={14} /> Friction Points
</h3>
{analysis.upcomingConflicts.map((c, i) => (
<div key={i} className="bg-orange-900/20 border border-orange-500/20 p-3 rounded-xl text-xs text-orange-200">
{c}
</div>
))}
</div>
)}
{/* Smart Nudges */}
<div className="space-y-3">
<h3 className="text-sm font-bold text-gray-400 uppercase tracking-wider flex items-center gap-2">
<Heart size={14} /> Smart Nudges
</h3>
{analysis.smartNudges.map((nudge, i) => (
<div key={i} className="bg-gradient-to-r from-blue-900/30 to-purple-900/30 border border-blue-500/20 p-3 rounded-xl flex items-center gap-3">
<MessageCircle size={16} className="text-blue-300" />
<p className="text-sm text-blue-100 italic">"{nudge}"</p>
</div>
))}
</div>
</>
) : null}
{/* Dream Journal Section - Always visible */}
<div className="border-t border-white/10 pt-6">
<h3 className="text-sm font-bold text-gray-400 uppercase tracking-wider flex items-center gap-2 mb-3">
<Moon size={14} /> Dream Journal
</h3>
<div className="bg-indigo-950/40 rounded-2xl p-4 border border-indigo-500/20">
<textarea
value={dreamText}
onChange={(e) => setDreamText(e.target.value)}
placeholder="I dreamt I was flying over a grocery store made of clouds..."
className="w-full bg-black/30 border-0 rounded-xl p-3 text-sm text-indigo-100 placeholder-indigo-400/50 resize-none h-20 focus:ring-1 focus:ring-indigo-500"
/>
<div className="flex justify-between items-center mt-3">
<span className="text-[10px] text-gray-500">AI Interpreter Ready</span>
<Button size="sm" onClick={handleInterpretDream} disabled={!dreamText || dreamLoading} className="text-xs py-1 px-3 bg-indigo-600 hover:bg-indigo-500">
{dreamLoading ? <Cloud className="animate-bounce" size={14}/> : 'Interpret'}
</Button>
</div>
{dreamResult && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-4 p-3 bg-white/5 rounded-xl border border-white/5">
<p className="text-xs text-purple-200 italic"> {dreamResult}</p>
</motion.div>
)}
</div>
</div>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
};