From 7d46edb86e29570f746a8a1964e3ad59dde1688d Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Fri, 20 Mar 2026 09:12:22 +0800 Subject: [PATCH 01/38] Extract public variables to config --- web/src/components/MoodCalendar.tsx | 12 +++++------ web/src/components/MoodCenter.tsx | 20 +++--------------- web/src/config/enum.ts | 32 ++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/web/src/components/MoodCalendar.tsx b/web/src/components/MoodCalendar.tsx index 184dc54..48e13e2 100644 --- a/web/src/components/MoodCalendar.tsx +++ b/web/src/components/MoodCalendar.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import type { Mood } from '../service/types'; -import { moodColorMap } from '../config/enum'; +import { emotionSpectrum, neutralColor, negativeColor, positiveColor } from '../config/enum'; interface MoodCalendarProps { currentDate: Date; @@ -41,8 +41,8 @@ export default function MoodCalendar({ const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`; const entry = entries.find((e) => e.recordedAt.startsWith(dateStr)); - const color = moodColorMap[entry?.mood]; - const bgColor = color ? `${color}33` : ''; + const color = entry?.mood ? emotionSpectrum[entry?.mood]?.color : '#99a1af'; + const bgColor = entry?.mood ? `${color}33` : '#fff'; days.push({ date: i, mood: entry?.mood ?? undefined, @@ -143,15 +143,15 @@ export default function MoodCalendar({
- + Positive
- + Neutral
- + Negative
diff --git a/web/src/components/MoodCenter.tsx b/web/src/components/MoodCenter.tsx index adbed6d..ac3a4b3 100644 --- a/web/src/components/MoodCenter.tsx +++ b/web/src/components/MoodCenter.tsx @@ -3,18 +3,7 @@ import { useAuth } from '../auth/AuthContext'; import { useToast } from './ToastProvider'; import { api } from '../service'; import type { Mood } from '../service/types'; - -// 情绪光谱系统配置 -const emotionSpectrum: Record = { - stress: { label: '压力', color: '#6B7280', icon: 'delete_outline' }, - boredom: { label: '无聊', color: '#9CA3AF', icon: 'hourglass_empty' }, - anxiety: { label: '焦虑', color: '#1E40AF', icon: 'cloud' }, - anger: { label: '愤怒', color: '#DC2626', icon: 'local_fire_department' }, - joy: { label: '快乐', color: '#F97316', icon: 'restaurant' }, - achievement: { label: '成就', color: '#EAB308', icon: 'star' }, - warmth: { label: '温暖', color: '#EC4899', icon: 'lightbulb' }, - calm: { label: '平静', color: '#22C55E', icon: 'eco' }, -}; +import { emotionSpectrum, positiveEmotions, negativeEmotions } from "../config/enum"; export default function MoodCenter() { const { user } = useAuth(); @@ -26,10 +15,7 @@ export default function MoodCenter() { const [positiveAnimating, setPositiveAnimating] = useState(false); const [negativeAnimating, setNegativeAnimating] = useState(false); - // 积极情绪列表 - const positiveEmotions = ['joy', 'achievement', 'warmth', 'calm']; - // 不积极情绪列表 - const negativeEmotions = ['stress', 'boredom', 'anxiety', 'anger']; + // 获取情绪历史 const fetchMoodHistory = useCallback(async () => { @@ -162,7 +148,7 @@ export default function MoodCenter() { {config?.icon || 'sentiment_satisfied'} - {config?.label} + {mood?.emotion}
); })} diff --git a/web/src/config/enum.ts b/web/src/config/enum.ts index c34c6e7..270751d 100644 --- a/web/src/config/enum.ts +++ b/web/src/config/enum.ts @@ -1,10 +1,22 @@ -export const moodColorMap = { - stress: "#6B7280", // gray-500 - boredom: "#9CA3AF", // gray-400 - anxiety: "#1E40AF", // blue-800 - anger: "#DC2626", // red-600 - joy: "#F97316", // orange-500 - achievement: "#EAB308", // yellow-500 - warmth: "#EC4899", // pink-500 - calm: "#22C55E", // green-500 -} \ No newline at end of file +// 情绪光谱系统配置 +export const emotionSpectrum: Record = { + stress: { color: '#6B7280', icon: 'delete_outline' }, + boredom: { color: '#9CA3AF', icon: 'hourglass_empty' }, + anxiety: { color: '#1E40AF', icon: 'cloud' }, + anger: { color: '#DC2626', icon: 'local_fire_department' }, + joy: { color: '#F97316', icon: 'restaurant' }, + achievement: { color: '#EAB308', icon: 'star' }, + warmth: { color: '#EC4899', icon: 'lightbulb' }, + calm: { color: '#22C55E', icon: 'eco' }, +}; + +// 积极情绪列表 +export const positiveEmotions = ['joy', 'achievement', 'warmth', 'calm']; +export const positiveColor = '#ff6900'; + +// 中间情绪颜色 +export const neutralColor = '#137fec'; + +// 不积极情绪列表 +export const negativeEmotions = ['stress', 'boredom', 'anxiety', 'anger']; +export const negativeColor = '#90a1b9'; \ No newline at end of file From f5f6c4397227809a4ee507f4f5acffa1750c8b34 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Fri, 20 Mar 2026 10:35:13 +0800 Subject: [PATCH 02/38] enhancement mood --- web/src/components/MoodCalendar.tsx | 13 ++++--------- web/src/config/enum.ts | 7 ++++--- web/src/pages/MyMood.tsx | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/web/src/components/MoodCalendar.tsx b/web/src/components/MoodCalendar.tsx index 48e13e2..c27eecd 100644 --- a/web/src/components/MoodCalendar.tsx +++ b/web/src/components/MoodCalendar.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import type { Mood } from '../service/types'; -import { emotionSpectrum, neutralColor, negativeColor, positiveColor } from '../config/enum'; +import { emotionSpectrum, neutralEmotions, neutralColor, negativeEmotions,negativeColor, positiveEmotions, positiveColor } from '../config/enum'; interface MoodCalendarProps { currentDate: Date; @@ -115,6 +115,8 @@ export default function MoodCalendar({ currentDate.getMonth() === new Date().getMonth(); const moodColor = day.color; + const moodStyle = moodColor && isCurrentMonth ? { backgroundColor: day.backgroundColor, color: day.color } : undefined; + const cellStyle = day.mood ? moodStyle : {border: `1px dashed #cad5e2`}; return (
{day.date}
diff --git a/web/src/config/enum.ts b/web/src/config/enum.ts index 270751d..26c1b9d 100644 --- a/web/src/config/enum.ts +++ b/web/src/config/enum.ts @@ -11,11 +11,12 @@ export const emotionSpectrum: Record = }; // 积极情绪列表 -export const positiveEmotions = ['joy', 'achievement', 'warmth', 'calm']; +export const positiveEmotions = ['joy', 'achievement', 'warmth']; export const positiveColor = '#ff6900'; -// 中间情绪颜色 -export const neutralColor = '#137fec'; +// 中间情绪列表 +export const neutralEmotions = ['calm']; +export const neutralColor = '#22C55E'; // 不积极情绪列表 export const negativeEmotions = ['stress', 'boredom', 'anxiety', 'anger']; diff --git a/web/src/pages/MyMood.tsx b/web/src/pages/MyMood.tsx index da43756..766c32c 100644 --- a/web/src/pages/MyMood.tsx +++ b/web/src/pages/MyMood.tsx @@ -141,7 +141,7 @@ export default function MyMood() {
{/* Title Section */}
-

Your Mood Journey

+

My Mood Journey

Visualizing your emotional well-being over the last {timeRange} days.

From 9a24cb18a358fec7aadff374c30b9c753124de19 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Fri, 20 Mar 2026 14:13:35 +0800 Subject: [PATCH 03/38] consitant mood Spectrum --- web/src/components/MoodCalendar.tsx | 2 +- web/src/components/MoodCenter.tsx | 12 +++-- web/src/components/SentimentDistribution.tsx | 19 ++------ web/src/config/enum.ts | 18 ++++---- web/src/pages/GroupMood.tsx | 18 +------- web/src/pages/MyMood.tsx | 48 +++++++++----------- web/src/service/moods.ts | 4 +- 7 files changed, 47 insertions(+), 74 deletions(-) diff --git a/web/src/components/MoodCalendar.tsx b/web/src/components/MoodCalendar.tsx index c27eecd..b5ae63b 100644 --- a/web/src/components/MoodCalendar.tsx +++ b/web/src/components/MoodCalendar.tsx @@ -61,7 +61,7 @@ export default function MoodCalendar({ }; return ( -
+

{formatMonth(currentDate)} diff --git a/web/src/components/MoodCenter.tsx b/web/src/components/MoodCenter.tsx index ac3a4b3..447cf18 100644 --- a/web/src/components/MoodCenter.tsx +++ b/web/src/components/MoodCenter.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useAuth } from '../auth/AuthContext'; import { useToast } from './ToastProvider'; import { api } from '../service'; @@ -8,6 +9,7 @@ import { emotionSpectrum, positiveEmotions, negativeEmotions } from "../config/e export default function MoodCenter() { const { user } = useAuth(); const toast = useToast(); + const navigate = useNavigate(); const [inputText, setInputText] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [moodHistory, setMoodHistory] = useState([]); @@ -94,14 +96,16 @@ export default function MoodCenter() {

-
- restaurant +
navigate('/my-mood?kind=positive')} className={`size-12 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-500 ${positiveAnimating ? 'animate-glow' : ''}`}> + + +
Candy Jar

Store joy

-
+
navigate('/my-mood?kind=negative')} className={`size-12 rounded-full bg-slate-100 dark:bg-slate-900/50 flex items-center justify-center text-slate-400 ${negativeAnimating ? 'animate-shake' : ''}`}> delete_outline
Recycle diff --git a/web/src/components/SentimentDistribution.tsx b/web/src/components/SentimentDistribution.tsx index 72b4159..bc12125 100644 --- a/web/src/components/SentimentDistribution.tsx +++ b/web/src/components/SentimentDistribution.tsx @@ -1,24 +1,11 @@ import type { TeamMoodDistribution } from '../service/types'; +import { emotionSpectrum } from "../config/enum"; + interface SentimentDistributionProps { distribution: TeamMoodDistribution[]; } -const emotionLabels: Record = { - joyful: 'Joyful', - calm: 'Calm', - anxious: 'Anxious', - stressed: 'Stressed', - excited: 'Excited', - tired: 'Tired', - grateful: 'Grateful', - frustrated: 'Frustrated', - positive: 'Positive', - neutral: 'Neutral', - negative: 'Negative', - productive: 'Productive', -}; - const pieColors = ['#22C55E', '#EAB308', '#DC2626', '#F97316', '#1E40AF', '#EC4899', '#9CA3AF', '#6B7280']; export default function SentimentDistribution({ distribution }: SentimentDistributionProps) { @@ -60,7 +47,7 @@ export default function SentimentDistribution({ distribution }: SentimentDistrib style={{ backgroundColor: pieColors[index % pieColors.length] }} /> - {emotionLabels[item.emotion] || item.emotion} ({item.percentage}%) + {emotionSpectrum[item.emotion]?.label || item.emotion} ({item.percentage}%)
))} diff --git a/web/src/config/enum.ts b/web/src/config/enum.ts index 26c1b9d..3effcc5 100644 --- a/web/src/config/enum.ts +++ b/web/src/config/enum.ts @@ -1,13 +1,13 @@ // 情绪光谱系统配置 -export const emotionSpectrum: Record = { - stress: { color: '#6B7280', icon: 'delete_outline' }, - boredom: { color: '#9CA3AF', icon: 'hourglass_empty' }, - anxiety: { color: '#1E40AF', icon: 'cloud' }, - anger: { color: '#DC2626', icon: 'local_fire_department' }, - joy: { color: '#F97316', icon: 'restaurant' }, - achievement: { color: '#EAB308', icon: 'star' }, - warmth: { color: '#EC4899', icon: 'lightbulb' }, - calm: { color: '#22C55E', icon: 'eco' }, +export const emotionSpectrum: Record = { + stress: { label: 'Stressed', color: '#6B7280', icon: 'delete_outline' }, + boredom: { label: 'Boredom', color: '#9CA3AF', icon: 'hourglass_empty' }, + anxiety: { label: 'Anxious', color: '#1E40AF', icon: 'cloud' }, + anger: { label: 'Anger', color: '#DC2626', icon: 'local_fire_department' }, + joy: { label: 'Joyful', color: '#F97316', icon: 'restaurant' }, + achievement: { label: 'Achievement', color: '#EAB308', icon: 'star' }, + warmth: { label: 'Warmth', color: '#EC4899', icon: 'lightbulb' }, + calm: { label: 'Calm', color: '#22C55E', icon: 'eco' }, }; // 积极情绪列表 diff --git a/web/src/pages/GroupMood.tsx b/web/src/pages/GroupMood.tsx index 6da07c5..e82014e 100644 --- a/web/src/pages/GroupMood.tsx +++ b/web/src/pages/GroupMood.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useAuth } from '../auth/AuthContext'; import { api } from '../service'; import type { TeamMoodStats, TeamMoodDistribution, TeamMoodTrend, TeamInsights } from '../service/types'; +import { emotionSpectrum } from "../config/enum"; import GroupMoodCalendar from '../components/GroupMoodCalendar'; import GroupMoodTrendChart from '../components/GroupMoodTrendChart'; import SentimentDistribution from '../components/SentimentDistribution'; @@ -10,21 +11,6 @@ import TeamInsightsCard from '../components/TeamInsights'; import GroupActionableAdvice from '../components/GroupActionableAdvice'; import StatCard from '../components/StatCard'; -// 情绪标签映射 -const emotionLabels: Record = { - joyful: 'Joyful', - calm: 'Calm', - anxious: 'Anxious', - stressed: 'Stressed', - excited: 'Excited', - tired: 'Tired', - grateful: 'Grateful', - frustrated: 'Frustrated', - positive: 'Positive', - neutral: 'Neutral', - negative: 'Negative', - productive: 'Productive', -}; export default function GroupMood() { const navigate = useNavigate(); @@ -175,7 +161,7 @@ export default function GroupMood() { title="Average Sentiment" icon="sentiment_satisfied" iconColor="text-green-500" - value={stats?.topEmotion ? emotionLabels[stats.topEmotion] || stats.topEmotion : 'Neutral'} + value={stats?.topEmotion ? emotionSpectrum[stats.topEmotion]?.label || stats.topEmotion : 'Neutral'} footer={ <> trending_up diff --git a/web/src/pages/MyMood.tsx b/web/src/pages/MyMood.tsx index 766c32c..069c837 100644 --- a/web/src/pages/MyMood.tsx +++ b/web/src/pages/MyMood.tsx @@ -6,24 +6,16 @@ import type { Mood } from '../service/types'; import MoodCalendar from '../components/MoodCalendar'; import MoodTrendChart from '../components/MoodTrendChart'; +import { emotionSpectrum } from "../config/enum"; + + interface MoodStats { total: number; - currentStreak: number; - topEmotion: string | null; + streakDays: number; + mostFrequentMood: string | null; moodDistribution: Record; } -const emotionLabels: Record = { - joyful: 'Joyful', - calm: 'Calm', - anxious: 'Anxious', - stressed: 'Stressed', - excited: 'Excited', - tired: 'Tired', - grateful: 'Grateful', - frustrated: 'Frustrated', -}; - export default function MyMood() { const navigate = useNavigate(); const { user } = useAuth(); @@ -153,7 +145,7 @@ export default function MyMood() {

Current Streak

-

{stats?.currentStreak || 0} Days

+

{stats?.streakDays || 0} Days

@@ -171,22 +163,26 @@ export default function MyMood() {

Top Emotion

-

{stats?.topEmotion ? emotionLabels[stats.topEmotion] || stats.topEmotion : 'None'}

+

{stats?.mostFrequentMood ? emotionSpectrum[stats.mostFrequentMood]?.label || stats.mostFrequentMood : 'None'}

{/* Main Grid: Calendar and Trend */}
- {/* Calendar Section */} - - - {/* Mood Trend Chart Section */} -
+
+
+
+ +
+ {/* Calendar Section */} + + + {/* Mood Trend Chart Section */}

Weekly Mood Insight

- {stats?.topEmotion - ? `You've been feeling mostly ${emotionLabels[stats.topEmotion] || stats.topEmotion.toLowerCase()}. Keep tracking your mood to discover patterns!` + {stats?.mostFrequentMood + ? `You've been feeling mostly ${emotionSpectrum[stats.mostFrequentMood].label || stats.mostFrequentMood.toLowerCase()}. Keep tracking your mood to discover patterns!` : 'Start logging your mood daily to receive personalized insights about your emotional patterns.'}

+
+
+ +
+ +
+User profile +
+
+
+
+ +
+ +
+

Your Mood Journey

+

Visualizing your emotional well-being over the last 30 days.

+
+ +
+
+
+local_fire_department +
+
+

Current Streak

+

12 Days

+
+
+
+
+calendar_month +
+
+

Total Recorded

+

128 Entries

+
+
+
+
+sentiment_very_satisfied +
+
+

Top Emotion

+

Joyful

+
+
+
+
+ +
+ +
+
+
+favorite +

It's Okay to Feel This Way

+
+

+ Some days are heavier than others. Take a deep breath—you are doing enough. You don't have to carry it all today. +

+
+
+self_improvement +
+
+ +
+
+
+

Release Your Worries

+

Feeling overwhelmed? Let's clear some mental space.

+
+
+
+ +
+
+
+Work stress +Overthinking +Deadline +Tiredness +Social anxiety +Poor sleep +
+
+
+delete_outline +
+
+
+ +
+
+

+ "Letting go isn't giving up, it's making room for better things." +

+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+

October 2023

+
+ + +
+
+
+
S
+
M
+
T
+
W
+
T
+
F
+
S
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
+
+
+ +Positive +
+
+ +Neutral +
+
+ +Negative +
+
+ +Missed Day +
+
+
+ +
+
+

Mood Trend

+ +
+
+
+AWESOME +GOOD +NEUTRAL +LOW +POOR +
+
+ + + + + + + + + + + + + +
+
+SEP 15 +SEP 22 +SEP 29 +OCT 06 +TODAY +
+
+
+ +
+
+lightbulb +
+
+

Weekly Mood Insight

+

+ You tend to feel more energized and positive on Wednesdays and Thursdays. This correlates with your consistent gym check-ins on those days. +

+ +
+
+
+
+
+ +
+ + + + + +
+
+ \ No newline at end of file diff --git a/web/src/components/MomentumCard.tsx b/web/src/components/MomentumCard.tsx index 84aff0c..eff8543 100644 --- a/web/src/components/MomentumCard.tsx +++ b/web/src/components/MomentumCard.tsx @@ -1,20 +1,41 @@ interface MomentumCardProps { checkInDays: number; - moodStatus?: 'great' | 'good' | 'neutral' | 'low'; + moodStatus?: 'positive' | 'negative' | 'neutral'; } export default function MomentumCard({ checkInDays, - moodStatus = 'great' + moodStatus = 'positive' }: MomentumCardProps) { + // Negative mood: supportive card + + + // Positive and neutral mood: momentum card const moodLabels: Record = { - great: { text: 'great', colorClass: 'text-orange-500' }, - good: { text: 'good', colorClass: 'text-emerald-500' }, - neutral: { text: 'okay', colorClass: 'text-primary' }, - low: { text: 'low', colorClass: 'text-slate-500' } + positive: { text: 'great', colorClass: 'text-orange-500' }, + neutral: { text: 'okay', colorClass: 'text-primary' } }; - const mood = moodLabels[moodStatus] || moodLabels.great; + const mood = moodLabels[moodStatus] || moodLabels.positive; + + if (moodStatus === 'negative') { + return ( +
+
+
+ favorite +

It's Okay to Feel This Way

+
+

+ Some days are heavier than others. Take a deep breath—you are doing enough. You don't have to carry it all today. +

+
+
+ self_improvement +
+
+ ); + } return (
diff --git a/web/src/pages/MyMood.tsx b/web/src/pages/MyMood.tsx index d400a1b..516a35c 100644 --- a/web/src/pages/MyMood.tsx +++ b/web/src/pages/MyMood.tsx @@ -13,8 +13,9 @@ import { MIN_PUZZLE_DAYS } from "../config/constants"; import { generateDayMood } from "../tools/functions"; -const getDaysByKind = (list:[], kind:string) => { +const getDaysByKind = (obj:Record, kind:string) => { let result = 0; + const list = Object.values(obj); list.forEach((v)=>{ if (kind == v) { result += 1; @@ -27,7 +28,7 @@ export default function MyMood() { const navigate = useNavigate(); const { user } = useAuth(); const [searchParams] = useSearchParams(); - const moodKind = searchParams.get('kind') || ''; + const moodKind = searchParams.get('kind') || 'positive'; const [entries, setEntries] = useState([]); const [stats, setStats] = useState(null); @@ -57,8 +58,8 @@ export default function MyMood() { setEntries(entriesData); setStats(statsData); - const dayMood = generateDayMood(entriesData) - const days = getDaysByKind(Object.values(dayMood), moodKind); + const dayMood = generateDayMood(entriesData); + const days = getDaysByKind(dayMood, moodKind); setKindCheckInDays(days); setError(null); } catch (err) { @@ -192,7 +193,7 @@ export default function MyMood() { /> {/* Emotional Puzzle */} - + />} + + {moodKind == 'negative' && (<>)} +
From 29db6a5f2d511eae542df426af682f7d9dc4bc57 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Tue, 24 Mar 2026 23:08:38 +0800 Subject: [PATCH 10/38] typo --- design/my_mood_negative.html | 143 ++++++---- design/my_mood_negative_v1.html | 406 ++++++++++++++++++++++++++++ web/src/components/WorryRelease.tsx | 185 +++++++++++++ web/src/config/constants.ts | 3 + web/src/pages/MyMood.tsx | 8 +- 5 files changed, 686 insertions(+), 59 deletions(-) create mode 100644 design/my_mood_negative_v1.html create mode 100644 web/src/components/WorryRelease.tsx diff --git a/design/my_mood_negative.html b/design/my_mood_negative.html index 1d2d1af..e00e59b 100644 --- a/design/my_mood_negative.html +++ b/design/my_mood_negative.html @@ -33,8 +33,7 @@ }, } - + .dark .bin-lid-top { background: #475569; } + .dark .bin-lid-handle { background: #64748b; } + .dark .bin-panel { background: rgba(255,255,255,0.05); } + .dark .worry-item { background: #0f172a; color: #cbd5e1; }
@@ -212,39 +233,47 @@

Release Your Worrie

-
-
-
-Work stress -Overthinking -Deadline -Tiredness -Social anxiety -Poor sleep -
-
-
-delete_outline +
+
+
+

+ "Letting go isn't giving up, it's making room for better things." +

+
+ +
+
+ +
+ +
+
+
+
+
+ +
+Work stress +Overthinking +Deadline +Tiredness +Social anxiety +Poor sleep +Expectations +Finances
- -
-
-

- "Letting go isn't giving up, it's making room for better things." -

-
-
+
diff --git a/design/my_mood_negative_v1.html b/design/my_mood_negative_v1.html new file mode 100644 index 0000000..1d2d1af --- /dev/null +++ b/design/my_mood_negative_v1.html @@ -0,0 +1,406 @@ + + + + + +Mood History - WIDEA + + + + + + + +
+ +
+
+
+ +
+
+ +
+ +
+User profile +
+
+
+
+
+
+ +
+

Your Mood Journey

+

Visualizing your emotional well-being over the last 30 days.

+
+ +
+
+
+local_fire_department +
+
+

Current Streak

+

12 Days

+
+
+
+
+calendar_month +
+
+

Total Recorded

+

128 Entries

+
+
+
+
+sentiment_very_satisfied +
+
+

Top Emotion

+

Joyful

+
+
+
+
+ +
+ +
+
+
+favorite +

It's Okay to Feel This Way

+
+

+ Some days are heavier than others. Take a deep breath—you are doing enough. You don't have to carry it all today. +

+
+
+self_improvement +
+
+ +
+
+
+

Release Your Worries

+

Feeling overwhelmed? Let's clear some mental space.

+
+
+
+ +
+
+
+Work stress +Overthinking +Deadline +Tiredness +Social anxiety +Poor sleep +
+
+
+delete_outline +
+
+
+ +
+
+

+ "Letting go isn't giving up, it's making room for better things." +

+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+

October 2023

+
+ + +
+
+
+
S
+
M
+
T
+
W
+
T
+
F
+
S
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
+
+
+ +Positive +
+
+ +Neutral +
+
+ +Negative +
+
+ +Missed Day +
+
+
+ +
+
+

Mood Trend

+ +
+
+
+AWESOME +GOOD +NEUTRAL +LOW +POOR +
+
+ + + + + + + + + + + + + +
+
+SEP 15 +SEP 22 +SEP 29 +OCT 06 +TODAY +
+
+
+ +
+
+lightbulb +
+
+

Weekly Mood Insight

+

+ You tend to feel more energized and positive on Wednesdays and Thursdays. This correlates with your consistent gym check-ins on those days. +

+ +
+
+
+
+
+ +
+ + + + + +
+
+ \ No newline at end of file diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx new file mode 100644 index 0000000..e4d03d4 --- /dev/null +++ b/web/src/components/WorryRelease.tsx @@ -0,0 +1,185 @@ +import { useState } from 'react'; + + +const worryItems = [ + { text: 'Work stress', rotation: -5 }, + { text: 'Overthinking', rotation: 3 }, + { text: 'Deadline', rotation: -2 }, + { text: 'Tiredness', rotation: 4 }, + { text: 'Social anxiety', rotation: -8 }, + { text: 'Poor sleep', rotation: 2 }, +]; + + +interface WorryReleaseProps { + completedDays?: number; + totalDays?: number; + onDump?: () => void; +} + +export default function WorryRelease({ + completedDays = 0, + totalDays = 0, + onDump + }: WorryReleaseProps) { + const [isReleasing, setIsReleasing] = useState(false); + const [showThankYou, setShowThankYou] = useState(false); + + const handleRelease = () => { + setIsReleasing(true); + setTimeout(() => { + setShowThankYou(true); + if(onDump){ + onDump() + } + }, 1000); + }; + + return ( +
+
+
+

Release Your Worries

+

Feeling overwhelmed? Let's clear some mental space.

+
+
+ +
+ {/* Bulging Trash Bin Visual */} +
+
+ {/* Bulging Content */} +
+ {worryItems.map((item, index) => ( + + {item.text} + + ))} +
+ + {/* Trash Bin */} +
+
+ delete_outline +
+
+
+ + {/* Quote Overlay */} + {!showThankYou ? ( +
+
+

+ “Letting go isn't giving up, it's making room for better things.” +

+
+
+ ) : ( +
+
+ check_circle +

Worries Released

+

+ Take a deep breath. You've taken the first step toward a lighter mind. +

+
+
+ )} + + {/* Release Button */} + {!showThankYou && ( +
+ +
+ )} +
+
+ + {/* CSS for the trash bin animation */} + +
+ ); +} diff --git a/web/src/config/constants.ts b/web/src/config/constants.ts index e8f7113..9dcc684 100644 --- a/web/src/config/constants.ts +++ b/web/src/config/constants.ts @@ -3,3 +3,6 @@ export const TEST_USER_ID = 'cmlkbymlh0000bphs80736xpr'; // 最少积攒多少天可以获得奖励 export const MIN_PUZZLE_DAYS = 7; + +// 最少积攒多少天的坏心情 就可以倾倒 +export const MIN_NEGATIVE_DAYS = 7; diff --git a/web/src/pages/MyMood.tsx b/web/src/pages/MyMood.tsx index 516a35c..b52dfa7 100644 --- a/web/src/pages/MyMood.tsx +++ b/web/src/pages/MyMood.tsx @@ -7,9 +7,10 @@ import MoodCalendar from '../components/MoodCalendar'; import MoodTrendChart from '../components/MoodTrendChart'; import MomentumCard from '../components/MomentumCard'; import EmotionalPuzzle from '../components/EmotionalPuzzle'; +import WorryRelease from '../components/WorryRelease'; import { emotionSpectrum} from '../config/enum'; -import { MIN_PUZZLE_DAYS } from "../config/constants"; +import { MIN_PUZZLE_DAYS, MIN_NEGATIVE_DAYS } from "../config/constants"; import { generateDayMood } from "../tools/functions"; @@ -203,7 +204,10 @@ export default function MyMood() { }} />} - {moodKind == 'negative' && (<>)} + {moodKind == 'negative' && }
From 9c17453255b8dae94d9cce1e890218cb30977d1a Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Wed, 25 Mar 2026 17:27:31 +0800 Subject: [PATCH 11/38] add animation --- web/src/components/WorryRelease.tsx | 183 +++++++++++++++++++--------- 1 file changed, 124 insertions(+), 59 deletions(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index e4d03d4..854d849 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -2,12 +2,14 @@ import { useState } from 'react'; const worryItems = [ - { text: 'Work stress', rotation: -5 }, - { text: 'Overthinking', rotation: 3 }, - { text: 'Deadline', rotation: -2 }, - { text: 'Tiredness', rotation: 4 }, - { text: 'Social anxiety', rotation: -8 }, - { text: 'Poor sleep', rotation: 2 }, + { text: 'Work stress', rotation: -12 }, + { text: 'Overthinking', rotation: 8 }, + { text: 'Deadline', rotation: -5 }, + { text: 'Tiredness', rotation: 15 }, + { text: 'Social anxiety', rotation: -10 }, + { text: 'Poor sleep', rotation: 4 }, + { text: 'Expectations', rotation: -18 }, + { text: 'Finances', rotation: 7 }, ]; @@ -44,30 +46,39 @@ export default function WorryRelease({
-
+
{/* Bulging Trash Bin Visual */}
-
- {/* Bulging Content */} -
- {worryItems.map((item, index) => ( - - {item.text} - - ))} -
+
+ {/* Detailed Premium Lid */} +
+
- {/* Trash Bin */} + {/* Trash Bin Body */}
-
- delete_outline + {/* 5 Vertical Panels */} +
+
+
+
+
+ + {/* Worries Inside (80% transparent via CSS) */} +
+ {worryItems.map((item, index) => ( +
+ + + {item.text} + +
+ ))}
@@ -126,59 +137,113 @@ export default function WorryRelease({ .trash-bin { position: relative; - width: 180px; - height: 240px; + width: 200px; + height: 260px; background: #cbd5e1; - border-radius: 20px 20px 40px 40px; - box-shadow: inset -15px 0 20px rgba(0,0,0,0.1); + border-radius: 4px 4px 40px 40px; + box-shadow: inset -10px -10px 20px rgba(0,0,0,0.05), 0 10px 25px -5px rgba(0,0,0,0.1); z-index: 10; + display: flex; + justify-content: space-evenly; + padding: 20px 10px 0; + overflow: hidden; + } + + .bin-panel { + width: 15%; + height: 80%; + margin-top: auto; + margin-bottom: auto; + background: rgba(0,0,0,0.05); + border-radius: 2px; + } + + .bin-contents { + position: absolute; + bottom: 5px; + left: 0; + right: 0; + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 10px; + gap: 4px; + z-index: 11; + opacity: 0.4; } - .trash-bin::before { - content: ''; + /* Premium Lid Design */ + .bin-lid-top { position: absolute; - top: -10px; - left: -10px; - right: -10px; + top: -15px; + left: -15px; + right: -15px; height: 20px; background: #94a3b8; - border-radius: 10px; + border-radius: 12px; + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); + z-index: 15; } - .bulge-content { + .bin-lid-handle { position: absolute; - top: -60px; + top: -25px; left: 50%; transform: translateX(-50%); - width: 220px; - height: 120px; - background: #e2e8f0; - border-radius: 50% 50% 0 0; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-content: flex-start; - padding-top: 20px; - overflow: visible; - z-index: 5; + width: 60px; + height: 12px; + background: #64748b; + border-radius: 6px 6px 0 0; + z-index: 14; + } + + .worry-wrapper { + transition: transform 0.2s ease-in-out; + } + .trash-bin:hover .worry-wrapper:nth-child(n) { + transform: translateY(-14px); + } + .trash-bin:hover .worry-wrapper:nth-last-child(1) { + transform: translateY(-3px); + } + .trash-bin:hover .worry-wrapper:nth-last-child(2) { + transform: translateY(-6px); + } + .trash-bin:hover .worry-wrapper:nth-last-child(3) { + transform: translateY(-8px); } + .worry-item { - font-size: 10px; - font-weight: 600; - color: #64748b; + display: inline-block; + font-size: 14px; + font-weight: 800; + color: #314158; background: white; - padding: 4px 8px; - border-radius: 12px; - margin: 2px; - box-shadow: 0 2px 4px rgba(0,0,0,0.05); + padding: 6px 12px; + border-radius: 4px; + box-shadow: 0 1px 2px rgba(0,0,0,0.1); white-space: nowrap; } + @keyframes shake { + 0%, 100% { transform: rotate(0deg); } + 20% { transform: rotate(-6deg); } + 40% { transform: rotate(6deg); } + 60% { transform: rotate(-4deg); } + 80% { transform: rotate(4deg); } + } + + @keyframes jampp { + 0%, 100% { transform: translateY(0px); } + 10% { transform: translateY(-6px); } + } + .dark .trash-bin { background: #334155; } - .dark .trash-bin::before { background: #475569; } - .dark .bulge-content { background: #1e293b; } - .dark .worry-item { background: #0f172a; color: #94a3b8; } + .dark .bin-lid-top { background: #475569; } + .dark .bin-lid-handle { background: #64748b; } + .dark .bin-panel { background: rgba(255,255,255,0.05); } + .dark .worry-item { background: #0f172a; color: #cbd5e1; } `}
); From e82380b86e6bd4ed75def8ead35a4c2df4088f6f Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Wed, 25 Mar 2026 21:10:03 +0800 Subject: [PATCH 12/38] add animation --- web/src/components/WorryRelease.tsx | 64 +++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index 854d849..a097918 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -49,10 +49,12 @@ export default function WorryRelease({
{/* Bulging Trash Bin Visual */}
-
+
{/* Detailed Premium Lid */} -
-
+
+
+
+
{/* Trash Bin Body */}
@@ -173,12 +175,15 @@ export default function WorryRelease({ } /* Premium Lid Design */ - .bin-lid-top { + .bin-lid { position: absolute; top: -15px; left: -15px; right: -15px; + } + .bin-lid-top { height: 20px; + width: 100%; background: #94a3b8; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); @@ -186,10 +191,6 @@ export default function WorryRelease({ } .bin-lid-handle { - position: absolute; - top: -25px; - left: 50%; - transform: translateX(-50%); width: 60px; height: 12px; background: #64748b; @@ -197,20 +198,30 @@ export default function WorryRelease({ z-index: 14; } + .bin-container-wrapper:hover .bin-lid{ + animation: shake 0.6s ease-in-out; + } + .worry-wrapper { transition: transform 0.2s ease-in-out; } .trash-bin:hover .worry-wrapper:nth-child(n) { - transform: translateY(-14px); + animation: jampp-4 1.2s ease-in-out; } .trash-bin:hover .worry-wrapper:nth-last-child(1) { - transform: translateY(-3px); + animation: jampp 1.2s ease-in-out; } .trash-bin:hover .worry-wrapper:nth-last-child(2) { - transform: translateY(-6px); + animation: jampp-1 1.2s ease-in-out; } .trash-bin:hover .worry-wrapper:nth-last-child(3) { - transform: translateY(-8px); + animation: jampp-2 1.2s ease-in-out; + } + .trash-bin:hover .worry-wrapper:nth-last-child(4) { + animation: jampp-3 1.2s ease-in-out; + } + .trash-bin:hover .worry-wrapper:nth-last-child(5) { + animation: jampp-3 1.2s ease-in-out; } @@ -228,15 +239,34 @@ export default function WorryRelease({ @keyframes shake { 0%, 100% { transform: rotate(0deg); } - 20% { transform: rotate(-6deg); } - 40% { transform: rotate(6deg); } - 60% { transform: rotate(-4deg); } - 80% { transform: rotate(4deg); } + 20% { transform: rotate(-3deg); } + 40% { transform: rotate(3deg); } + 60% { transform: rotate(-2deg); } + 80% { transform: rotate(2deg); } } @keyframes jampp { 0%, 100% { transform: translateY(0px); } - 10% { transform: translateY(-6px); } + 15% { transform: translateY(-5px); } + } + + @keyframes jampp-1 { + 0%, 100% { transform: translateY(0px); } + 15% { transform: translateY(-6px); } + } + + @keyframes jampp-2 { + 0%, 100% { transform: translateY(0px); } + 15% { transform: translateY(-8px); } + } + + @keyframes jampp-3 { + 0%, 100% { transform: translateY(0px); } + 15% { transform: translateY(-14px); } + } + @keyframes jampp-4 { + 0%, 100% { transform: translateY(0px); } + 15% { transform: translateY(-20px); } } .dark .trash-bin { background: #334155; } From b46b6dc74bf7a764b2d74c829371047583e5d89c Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Wed, 25 Mar 2026 21:33:00 +0800 Subject: [PATCH 13/38] typo --- web/src/components/WorryRelease.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index a097918..bb307a6 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -46,10 +46,10 @@ export default function WorryRelease({
-
+
{/* Bulging Trash Bin Visual */}
-
+
{/* Detailed Premium Lid */}
From 0309ea7534e524229742d7d265e639a89ec23173 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Wed, 25 Mar 2026 22:18:25 +0800 Subject: [PATCH 14/38] typo --- web/src/components/WorryRelease.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index bb307a6..1dde79d 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -66,7 +66,7 @@ export default function WorryRelease({
{/* Worries Inside (80% transparent via CSS) */} -
+
{worryItems.map((item, index) => (
@@ -173,6 +173,9 @@ export default function WorryRelease({ z-index: 11; opacity: 0.4; } + .bin-contents.opacity-0 { + opacity: 0; + } /* Premium Lid Design */ .bin-lid { From 7ceac384394448f773827baffdcfea0c7ea08819 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Thu, 26 Mar 2026 08:46:40 +0800 Subject: [PATCH 15/38] open --- web/src/components/WorryRelease.tsx | 35 +++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index 1dde79d..7ef56e5 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -51,7 +51,7 @@ export default function WorryRelease({
{/* Detailed Premium Lid */} -
+
@@ -66,13 +66,19 @@ export default function WorryRelease({
{/* Worries Inside (80% transparent via CSS) */} -
+
{worryItems.map((item, index) => ( -
- +
Date: Thu, 26 Mar 2026 08:58:43 +0800 Subject: [PATCH 16/38] add tilt --- web/src/components/WorryRelease.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index 7ef56e5..efdf7f9 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -49,7 +49,7 @@ export default function WorryRelease({
{/* Bulging Trash Bin Visual */}
-
+
{/* Detailed Premium Lid */}
@@ -207,6 +207,19 @@ export default function WorryRelease({ z-index: 14; } + .bin-container-wrapper.lid-tilt { + animation: tilt 1s ease-in-out 0.6s forwards; + } + + @keyframes tilt { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(65deg); + } + } + .bin-container-wrapper:hover .bin-lid:not(.lid-open){ animation: shake 0.6s ease-in-out; } From 8e5eee9c98fd9f6489af5943c2f13a248b14309d Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Thu, 26 Mar 2026 19:41:01 +0800 Subject: [PATCH 17/38] finish release worries animation --- CLAUDE.md | 6 +- web/src/components/WorryRelease.tsx | 196 +++++++++++++++++----------- 2 files changed, 125 insertions(+), 77 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 281b275..7d81a33 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,4 +26,8 @@ api/src/ Packages (packages/) packages/ ├── db/ Database package for schema management, migrations, client generation, and seed workflows. -├── shared/ Shared package space for cross-package utilities/types \ No newline at end of file +├── shared/ Shared package space for cross-package utilities/types + + +禁止使用 npm, pnpm npx, yarn +只能使用 bun \ No newline at end of file diff --git a/web/src/components/WorryRelease.tsx b/web/src/components/WorryRelease.tsx index efdf7f9..958ebf3 100644 --- a/web/src/components/WorryRelease.tsx +++ b/web/src/components/WorryRelease.tsx @@ -25,16 +25,17 @@ export default function WorryRelease({ onDump }: WorryReleaseProps) { const [isReleasing, setIsReleasing] = useState(false); - const [showThankYou, setShowThankYou] = useState(false); + const [isReleased, setIsReleased] = useState(false); const handleRelease = () => { setIsReleasing(true); setTimeout(() => { - setShowThankYou(true); + setIsReleasing(false); + setIsReleased(true); if(onDump){ onDump() } - }, 1000); + }, 3000); }; return ( @@ -48,84 +49,83 @@ export default function WorryRelease({
{/* Bulging Trash Bin Visual */} -
-
- {/* Detailed Premium Lid */} -
-
-
-
- - {/* Trash Bin Body */} -
- {/* 5 Vertical Panels */} -
-
-
-
-
- - {/* Worries Inside (80% transparent via CSS) */} -
- {worryItems.map((item, index) => ( -
- - {item.text} - -
- ))} -
-
-
+
{/* Quote Overlay */} - {!showThankYou ? ( -
-
+ +
+ {!isReleased ?

“Letting go isn't giving up, it's making room for better things.”

-
-
- ) : ( -
-
+
:
check_circle

Worries Released

Take a deep breath. You've taken the first step toward a lighter mind.

+
} +
+ + + +
+
+ {/* Detailed Premium Lid */} +
+
+
+
+ + {/* Trash Bin Body */} +
+ {/* 5 Vertical Panels */} +
+
+
+
+
+ + {/* Worries Inside (80% transparent via CSS) */} + {!isReleased &&
+ {worryItems.map((item, index) => ( +
+ + {item.text} + +
+ ))} +
} +
- )} +
{/* Release Button */} - {!showThankYou && ( -
- -
- )} +
+ +
+
@@ -133,14 +133,10 @@ export default function WorryRelease({
From 4dc33499d64157b5fe676bc977a9a991614f0397 Mon Sep 17 00:00:00 2001 From: eylu <1069888988@qq.com> Date: Sat, 4 Apr 2026 11:21:54 +0800 Subject: [PATCH 37/38] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E7=A7=AF=E6=9E=81?= =?UTF-8?q?=E6=83=85=E7=BB=AA=E5=85=91=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/controllers/moods.controller.ts | 25 ++++ api/src/routes/moods.routes.ts | 3 + packages/db/src/models.ts | 4 +- web/src/components/EmotionalPuzzle.tsx | 151 +++++++++++++++++++++--- web/src/components/WorryRelease.tsx | 16 ++- web/src/index.css | 2 +- web/src/pages/MyMood.tsx | 2 +- web/src/service/moods.ts | 12 ++ 8 files changed, 191 insertions(+), 24 deletions(-) diff --git a/api/src/controllers/moods.controller.ts b/api/src/controllers/moods.controller.ts index 491771f..7b059e5 100644 --- a/api/src/controllers/moods.controller.ts +++ b/api/src/controllers/moods.controller.ts @@ -291,6 +291,31 @@ export const moodsController = { } }, + // 正向情绪兑换 + async reward(req: Request) { + const data = (await req.json()) as { + userId: string; + }; + + if (!data.userId) { + return Response.json({ error: "userId is required" }, { status: 400 }); + } + + try { + const result = await moods.redeem(data.userId, 'reward'); + return Response.json(result); + } catch (error) { + console.error("Reward moods error:", error); + if (error instanceof Error) { + return Response.json({ error: error.message }, { status: 400 }); + } + return Response.json( + { error: "Failed to reward moods" }, + { status: 500 } + ); + } + }, + // 获取兑换历史 async getRedemptionHistory(req: Request) { const url = new URL(req.url); diff --git a/api/src/routes/moods.routes.ts b/api/src/routes/moods.routes.ts index 89f653f..32f83f7 100644 --- a/api/src/routes/moods.routes.ts +++ b/api/src/routes/moods.routes.ts @@ -40,6 +40,9 @@ export const moodsRoutes = { "/api/moods/redemption-history": { GET: moodsController.getRedemptionHistory, }, + "/api/moods/reward": { + POST: moodsController.reward, + }, "/api/moods/:id": { GET: moodsController.getById, }, diff --git a/packages/db/src/models.ts b/packages/db/src/models.ts index e59a3ce..bede517 100644 --- a/packages/db/src/models.ts +++ b/packages/db/src/models.ts @@ -790,8 +790,8 @@ export interface MoodRedemption { createdAt: Date; } -const POSITIVE_BASE = 7; -const NEGATIVE_BASE = 7; +const POSITIVE_BASE = 2; +const NEGATIVE_BASE = 2; export const moods = { async findById(id: string) { diff --git a/web/src/components/EmotionalPuzzle.tsx b/web/src/components/EmotionalPuzzle.tsx index cabe462..b7fc09c 100644 --- a/web/src/components/EmotionalPuzzle.tsx +++ b/web/src/components/EmotionalPuzzle.tsx @@ -1,6 +1,7 @@ import { useEffect, useState, useMemo } from "react"; import { api } from '../service'; -import type { MoodStats, RedemptionEligibility, RedemptionHistory } from '../service/types'; +import type { MoodStats, RedemptionEligibility, RedemptionHistory, RedemptionResult } from '../service/types'; +import { withMinDuration } from "../tools/functions"; // 拼图块类型 interface PuzzlePiece { @@ -19,6 +20,28 @@ interface EmotionalPuzzleProps { onGetReward?: () => void; } +// 预定义的励志名言列表 +const INSPIRATIONAL_QUOTES = [ + { text: "The greatest glory in living lies not in never falling, but in rising every time we fall.", author: "Nelson Mandela" }, + { text: "The way to get started is to quit talking and begin doing.", author: "Walt Disney" }, + { text: "Your time is limited, don't waste it living someone else's life.", author: "Steve Jobs" }, + { text: "Success is not final, failure is not fatal: it is the courage to continue that counts.", author: "Winston Churchill" }, + { text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" }, + { text: "The only impossible journey is the one you never begin.", author: "Tony Robbins" }, + { text: "Happiness is not something ready-made. It comes from your own actions.", author: "Dalai Lama" }, + { text: "Don't watch the clock; do what it does. Keep going.", author: "Sam Levenson" }, + { text: "Everything you've ever wanted is on the other side of fear.", author: "George Addair" }, + { text: "The best time to plant a tree was 20 years ago. The second best time is now.", author: "Chinese Proverb" }, + { text: "Your positive action combined with positive thinking results in success.", author: "Shiv Khera" }, + { text: "Act as if what you do makes a difference. It does.", author: "William James" }, +]; + +// 获取随机名言 +function getRandomQuote(): { text: string; author: string } { + const randomIndex = Math.floor(Math.random() * INSPIRATIONAL_QUOTES.length); + return INSPIRATIONAL_QUOTES[randomIndex]; +} + export default function EmotionalPuzzle({ stats, redemptionEligibility, @@ -28,6 +51,11 @@ export default function EmotionalPuzzle({ onGetReward }: EmotionalPuzzleProps) { + // 状态管理 + const [isRedeeming, setIsRedeeming] = useState(false); + const [isRedeemed, setIsRedeemed] = useState(false); + const [currentQuote, setCurrentQuote] = useState<{ text: string; author: string } | null>(null); + const [error, setError] = useState(null); // 加载 entries useEffect(() => { @@ -43,10 +71,52 @@ export default function EmotionalPuzzle({ // TODO // 完成 情绪显示 } catch (err) { + console.error("Failed to fetch entries:", err); + } + }; + + const handleGetReward = async () => { + // 检查兑换资格 + if (!userId || !redemptionEligibility?.positive.canRedeem) { + setError(`You need at least ${redemptionEligibility?.positive.base} positive mood days to get reward`); + return; + } + + setIsRedeeming(true); + setError(null); + + try { + // 调用API,至少显示3秒loading动画 + const result: RedemptionResult = await withMinDuration( + api.moods.rewardMoods(userId), + 3000 + ); + + // 兑换成功 + setIsRedeeming(false); + setIsRedeemed(true); + + // 随机选择一条名言 + const randomQuote = getRandomQuote(); + setCurrentQuote(randomQuote); + + // 刷新entries + fetchEntries(); + // 调用父组件回调 + if (onGetReward) { + onGetReward(); + } + } catch (err) { + setIsRedeeming(false); + setError(err instanceof Error ? err.message : 'Failed to get reward'); } }; + const handleCloseModal = () => { + setIsRedeemed(false); + }; + // 生成7块拼图 - 7块4/5边形拼成完整正方形,PC端500x500,手机端自适应 const puzzlePieces = useMemo(() => { // SVG 视图框大小,所有坐标基于此缩放 @@ -135,13 +205,6 @@ export default function EmotionalPuzzle({ return pieces; }, []); - - const handleGetReward = () => { - if (onGetReward) { - onGetReward(); - } - }; - return (
{/* Header */} @@ -165,6 +228,11 @@ export default function EmotionalPuzzle({
+ {error && ( +
+

{error}

+
+ )} {redemptionEligibility && (

@@ -191,7 +259,7 @@ export default function EmotionalPuzzle({ > index ? piece.color : '#ccc'} + fill={(redemptionEligibility?.positive.count || 0) > index ? piece.color : '#eee'} stroke={(redemptionEligibility?.positive.count || 0) > index ? piece.strokeColor : '#bbb'} strokeWidth="1" style={{ @@ -226,12 +294,21 @@ export default function EmotionalPuzzle({

@@ -239,6 +316,52 @@ export default function EmotionalPuzzle({
+ {/* Success Modal */} + {isRedeemed && currentQuote && ( +
+ {/* Backdrop */} +
+ + {/* Modal Content */} +
+ {/* Celebration Header */} +
+
🎉
+

Congratulations!

+

You've unlocked your reward

+
+ + {/* Quote Content */} +
+
"
+
+ {currentQuote.text} +
+ + — {currentQuote.author} + + + {/* Decorative Elements */} +
+ + + +
+
+ + {/* Footer with Close Button */} +
+ +
+
+
+ )} + {/* CSS Styles */}