Skip to content

Commit 575eecd

Browse files
Implement mood tracking feature
Adds mood tracking functionality with daily prompts and mood level recording. Saves journal entries to Firebase profile. Replaces community map quick action with journal.
1 parent 300425a commit 575eecd

File tree

4 files changed

+405
-9
lines changed

4 files changed

+405
-9
lines changed

src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
23
import { Toaster } from "@/components/ui/toaster";
34
import { Toaster as Sonner } from "@/components/ui/sonner";
45
import { TooltipProvider } from "@/components/ui/tooltip";
@@ -22,6 +23,7 @@ import NotFound from "./pages/NotFound";
2223
import Navbar from "./components/Navbar";
2324
import Profile from "./pages/Profile";
2425
import Goodbye from "./pages/Goodbye";
26+
import Journal from "./pages/Journal";
2527

2628
const queryClient = new QueryClient();
2729

@@ -91,6 +93,14 @@ const App = () => (
9193
</AuthWrapper>
9294
}
9395
/>
96+
<Route
97+
path="/journal"
98+
element={
99+
<AuthWrapper requireAuth>
100+
<Journal />
101+
</AuthWrapper>
102+
}
103+
/>
94104

95105
{/* Admin routes */}
96106
<Route

src/pages/Dashboard.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import React, { useState, useEffect } from 'react';
23
import { Link } from 'react-router-dom';
34
import { useAuth } from '../utils/auth';
@@ -9,8 +10,8 @@ import {
910
Trophy,
1011
TrendingUp,
1112
MessageCircle,
12-
Map,
13-
HeartPulse
13+
HeartPulse,
14+
BookOpen
1415
} from 'lucide-react';
1516
import { Button } from '@/components/ui/button';
1617
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
@@ -204,19 +205,19 @@ const Dashboard: React.FC = () => {
204205
</Link>
205206
</Button>
206207
<Button variant="outline" className="w-full justify-between" asChild>
207-
<Link to="/community">
208+
<Link to="/journal">
208209
<div className="flex items-center">
209-
<MessageCircle className="mr-2 h-4 w-4" />
210-
<span>Community Chat</span>
210+
<BookOpen className="mr-2 h-4 w-4" />
211+
<span>Journal</span>
211212
</div>
212213
<ArrowRight className="h-4 w-4" />
213214
</Link>
214215
</Button>
215216
<Button variant="outline" className="w-full justify-between" asChild>
216-
<Link to="/map">
217+
<Link to="/community">
217218
<div className="flex items-center">
218-
<Map className="mr-2 h-4 w-4" />
219-
<span>Community Map</span>
219+
<MessageCircle className="mr-2 h-4 w-4" />
220+
<span>Community Chat</span>
220221
</div>
221222
<ArrowRight className="h-4 w-4" />
222223
</Link>

src/pages/Journal.tsx

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
2+
import React, { useState, useEffect } from 'react';
3+
import { useNavigate } from 'react-router-dom';
4+
import { motion } from 'framer-motion';
5+
import {
6+
ArrowLeft,
7+
ChevronRight,
8+
HelpCircle,
9+
BookOpen
10+
} from 'lucide-react';
11+
import { Button } from '@/components/ui/button';
12+
import { Card, CardContent } from '@/components/ui/card';
13+
import { Slider } from '@/components/ui/slider';
14+
import { Textarea } from '@/components/ui/textarea';
15+
import { Badge } from '@/components/ui/badge';
16+
import { useAuth } from '../utils/auth';
17+
import { addJournalEntry, getJournalEntries } from '../utils/firebase';
18+
import { toast } from 'sonner';
19+
20+
// List of prompts to rotate through
21+
const DAILY_PROMPTS = [
22+
"What's one thing you're grateful for today?",
23+
"How did you handle urges or challenges today?",
24+
"What's something that brought you joy today?",
25+
"What's something you learned about yourself today?",
26+
"What's one small victory you had today?",
27+
"What's something you're looking forward to tomorrow?",
28+
"What's a way you showed kindness to yourself today?",
29+
"What's something that challenged you today and how did you respond?",
30+
"What's one positive change you've noticed in yourself recently?",
31+
"What did you do for self-care today?",
32+
];
33+
34+
// Negative emotions (for lower mood scores)
35+
const NEGATIVE_EMOTIONS = [
36+
"Angry", "Anxious", "Scared", "Overwhelmed", "Ashamed",
37+
"Disgusted", "Embarrassed", "Frustrated", "Annoyed",
38+
"Jealous", "Stressed", "Worried", "Guilty", "Surprised",
39+
"Hopeless", "Irritated", "Lonely", "Discouraged",
40+
"Disappointed", "Drained", "Sad"
41+
];
42+
43+
// Positive emotions (for higher mood scores)
44+
const POSITIVE_EMOTIONS = [
45+
"Amazed", "Excited", "Surprised", "Passionate", "Happy",
46+
"Joyful", "Brave", "Proud", "Confident", "Hopeful",
47+
"Amused", "Satisfied", "Relieved", "Grateful", "Content",
48+
"Calm", "Peaceful", "Inspired", "Loved", "Refreshed"
49+
];
50+
51+
// Get mood label based on score
52+
const getMoodLabel = (score: number): string => {
53+
if (score <= 2) return "Very Unpleasant";
54+
if (score <= 4) return "Slightly Unpleasant";
55+
if (score <= 6) return "Neutral";
56+
if (score <= 8) return "Slightly Pleasant";
57+
return "Very Pleasant";
58+
};
59+
60+
// Get background color based on mood score
61+
const getMoodBackground = (score: number): string => {
62+
if (score <= 2) return "bg-slate-800";
63+
if (score <= 4) return "bg-slate-700";
64+
if (score <= 6) return "bg-slate-600";
65+
if (score <= 8) return "bg-emerald-900";
66+
return "bg-emerald-800";
67+
};
68+
69+
// Get foreground color based on mood score
70+
const getMoodColor = (score: number): string => {
71+
if (score <= 4) return "text-blue-300";
72+
if (score <= 6) return "text-slate-200";
73+
return "text-green-300";
74+
};
75+
76+
const Journal: React.FC = () => {
77+
const navigate = useNavigate();
78+
const { currentUser } = useAuth();
79+
const [step, setStep] = useState(1);
80+
const [moodScore, setMoodScore] = useState(5);
81+
const [selectedEmotions, setSelectedEmotions] = useState<string[]>([]);
82+
const [journalText, setJournalText] = useState('');
83+
const [prompt, setPrompt] = useState('');
84+
const [isSubmitting, setIsSubmitting] = useState(false);
85+
86+
// Get a random prompt when component mounts
87+
useEffect(() => {
88+
const randomIndex = Math.floor(Math.random() * DAILY_PROMPTS.length);
89+
setPrompt(DAILY_PROMPTS[randomIndex]);
90+
}, []);
91+
92+
// Get emotions based on mood score
93+
const relevantEmotions = moodScore <= 5 ? NEGATIVE_EMOTIONS : POSITIVE_EMOTIONS;
94+
95+
const handleEmotionToggle = (emotion: string) => {
96+
if (selectedEmotions.includes(emotion)) {
97+
setSelectedEmotions(selectedEmotions.filter(e => e !== emotion));
98+
} else {
99+
// Limit to 3 emotions
100+
if (selectedEmotions.length < 3) {
101+
setSelectedEmotions([...selectedEmotions, emotion]);
102+
} else {
103+
toast.info("You can select up to 3 emotions");
104+
}
105+
}
106+
};
107+
108+
const handleSubmit = async () => {
109+
if (!currentUser) {
110+
toast.error("You must be logged in to journal");
111+
return;
112+
}
113+
114+
if (journalText.trim() === '') {
115+
toast.error("Please write something in your journal");
116+
return;
117+
}
118+
119+
setIsSubmitting(true);
120+
121+
try {
122+
await addJournalEntry({
123+
userId: currentUser.uid,
124+
timestamp: new Date(),
125+
question: prompt,
126+
notes: journalText,
127+
level: moodScore,
128+
emotions: selectedEmotions
129+
});
130+
131+
toast.success("Journal entry saved!");
132+
navigate('/dashboard');
133+
} catch (error) {
134+
console.error("Error saving journal entry:", error);
135+
toast.error("Failed to save journal entry");
136+
} finally {
137+
setIsSubmitting(false);
138+
}
139+
};
140+
141+
const moodLabel = getMoodLabel(moodScore);
142+
const backgroundClass = getMoodBackground(moodScore);
143+
const moodColorClass = getMoodColor(moodScore);
144+
145+
return (
146+
<div className={`min-h-screen ${backgroundClass} transition-colors duration-300`}>
147+
<div className="container max-w-md py-8 px-4 mx-auto">
148+
<div className="flex justify-between items-center mb-8">
149+
<Button
150+
variant="ghost"
151+
size="icon"
152+
className="text-white"
153+
onClick={() => {
154+
if (step > 1) {
155+
setStep(step - 1);
156+
} else {
157+
navigate('/dashboard');
158+
}
159+
}}
160+
>
161+
<ArrowLeft className="h-6 w-6" />
162+
</Button>
163+
164+
<div className={`text-xl font-medium ${moodColorClass}`}>
165+
{step === 1 ? 'How are you feeling?' : step === 2 ? 'Select emotions' : 'Write in journal'}
166+
</div>
167+
168+
<Button
169+
variant="ghost"
170+
className="text-white"
171+
disabled={step === 3 ? isSubmitting : false}
172+
onClick={() => {
173+
if (step < 3) {
174+
setStep(step + 1);
175+
} else {
176+
handleSubmit();
177+
}
178+
}}
179+
>
180+
{step === 3 ? 'Save' : 'Next'}
181+
{step < 3 && <ChevronRight className="ml-1 h-4 w-4" />}
182+
</Button>
183+
</div>
184+
185+
{step === 1 && (
186+
<motion.div
187+
initial={{ opacity: 0 }}
188+
animate={{ opacity: 1 }}
189+
exit={{ opacity: 0 }}
190+
className="space-y-8"
191+
>
192+
<div className="text-center">
193+
<h1 className="text-3xl font-bold text-white mb-2">
194+
Choose how you're feeling
195+
</h1>
196+
<p className="text-white/80">right now</p>
197+
</div>
198+
199+
<div className="flex flex-col items-center justify-center py-10">
200+
<div className="relative w-48 h-48 flex items-center justify-center mb-6">
201+
<div className={`absolute inset-0 rounded-full blur-lg bg-${moodScore > 5 ? 'green' : 'blue'}-400 opacity-20`}></div>
202+
<div className={`w-36 h-36 rounded-full ${moodScore > 5 ? 'bg-green-400/30' : 'bg-blue-400/30'} flex items-center justify-center`}>
203+
<div className={`w-24 h-24 rounded-full ${moodScore > 5 ? 'bg-green-400/50' : 'bg-blue-400/50'} flex items-center justify-center`}>
204+
<div className={`w-16 h-16 rounded-full ${moodScore > 5 ? 'bg-green-400/70' : 'bg-blue-400/70'} flex items-center justify-center`}>
205+
<div className={`w-8 h-8 rounded-full ${moodScore > 5 ? 'bg-green-400' : 'bg-blue-400'}`}></div>
206+
</div>
207+
</div>
208+
</div>
209+
</div>
210+
211+
<h2 className={`text-4xl font-bold mb-12 ${moodColorClass}`}>
212+
{moodLabel}
213+
</h2>
214+
215+
<div className="w-full px-4">
216+
<Slider
217+
value={[moodScore]}
218+
min={1}
219+
max={10}
220+
step={1}
221+
onValueChange={(values) => setMoodScore(values[0])}
222+
className="my-6"
223+
/>
224+
225+
<div className="flex justify-between text-white/70 text-sm">
226+
<span>VERY UNPLEASANT</span>
227+
<span>VERY PLEASANT</span>
228+
</div>
229+
</div>
230+
</div>
231+
</motion.div>
232+
)}
233+
234+
{step === 2 && (
235+
<motion.div
236+
initial={{ opacity: 0 }}
237+
animate={{ opacity: 1 }}
238+
exit={{ opacity: 0 }}
239+
className="space-y-6"
240+
>
241+
<div className="text-center">
242+
<h2 className={`text-4xl font-bold mb-2 ${moodColorClass}`}>
243+
{moodLabel}
244+
</h2>
245+
<p className="text-xl text-white/80 mb-8">
246+
What best describes this feeling?
247+
</p>
248+
<p className="text-sm text-white/60">
249+
Select up to 3 emotions
250+
</p>
251+
</div>
252+
253+
<div className="flex flex-wrap gap-2 justify-center">
254+
{relevantEmotions.map((emotion) => (
255+
<Badge
256+
key={emotion}
257+
variant={selectedEmotions.includes(emotion) ? "default" : "outline"}
258+
className={`text-md py-2 px-4 cursor-pointer ${
259+
selectedEmotions.includes(emotion)
260+
? moodScore > 5 ? 'bg-green-600 hover:bg-green-700' : 'bg-blue-600 hover:bg-blue-700'
261+
: 'hover:bg-white/10'
262+
}`}
263+
onClick={() => handleEmotionToggle(emotion)}
264+
>
265+
{emotion}
266+
</Badge>
267+
))}
268+
</div>
269+
</motion.div>
270+
)}
271+
272+
{step === 3 && (
273+
<motion.div
274+
initial={{ opacity: 0 }}
275+
animate={{ opacity: 1 }}
276+
exit={{ opacity: 0 }}
277+
className="space-y-6"
278+
>
279+
<Card className="bg-white/10 border-none">
280+
<CardContent className="p-4">
281+
<h3 className="text-xl font-medium text-white mb-2">Today's Prompt</h3>
282+
<p className="text-white/80 mb-4">{prompt}</p>
283+
<Textarea
284+
value={journalText}
285+
onChange={(e) => setJournalText(e.target.value)}
286+
placeholder="Write your thoughts here..."
287+
className="min-h-[200px] bg-white/5 border-white/20 text-white placeholder:text-white/50"
288+
/>
289+
</CardContent>
290+
</Card>
291+
292+
<div className="space-y-4">
293+
<h3 className="text-xl font-medium text-white">Your mood</h3>
294+
<div className="flex items-center gap-3">
295+
<div className={`w-10 h-10 rounded-full ${moodScore > 5 ? 'bg-green-400/70' : 'bg-blue-400/70'} flex-shrink-0`}></div>
296+
<div>
297+
<p className={`font-medium ${moodColorClass}`}>{moodLabel}</p>
298+
<div className="flex flex-wrap gap-1 mt-1">
299+
{selectedEmotions.map(emotion => (
300+
<Badge key={emotion} variant="outline" className="text-xs bg-white/10">
301+
{emotion}
302+
</Badge>
303+
))}
304+
</div>
305+
</div>
306+
</div>
307+
</div>
308+
</motion.div>
309+
)}
310+
</div>
311+
</div>
312+
);
313+
};
314+
315+
export default Journal;

0 commit comments

Comments
 (0)