Files
Nova40/src/screens/DailyJournalScreen.js
T
dios.one 23dc306f12 init
2026-04-21 18:00:30 +07:00

299 lines
8.9 KiB
JavaScript

import React, { useState, useRef, useEffect } from 'react';
import {
View, Text, StyleSheet, ScrollView, Pressable,
Animated, KeyboardAvoidingView, Platform,
} from 'react-native';
import { showAlert } from '../components/NovaAlert';
import ScreenWrapper from '../components/ScreenWrapper';
import Input from '../components/Input';
import Button from '../components/Button';
import useIdentityStore from '../store/useIdentityStore';
import useHabitStore from '../store/useHabitStore';
import { generateDailyReflection, saveDailyJournal } from '../services/journalService';
import { colors, fonts, spacing, borderRadius } from '../utils/theme';
const MOODS = [
{ key: 'great', emoji: '😄', label: 'Great' },
{ key: 'good', emoji: '🙂', label: 'Good' },
{ key: 'okay', emoji: '😐', label: 'Okay' },
{ key: 'bad', emoji: '😔', label: 'Bad' },
{ key: 'terrible', emoji: '😞', label: 'Terrible' },
];
const IDENTITY_OPTIONS = [
{ key: 'yes', label: 'Yes', color: colors.success },
{ key: 'almost', label: 'Almost', color: colors.warning },
{ key: 'no', label: 'No', color: colors.error },
];
export default function DailyJournalScreen({ navigation }) {
const identity = useIdentityStore((s) => s.identity);
const currentDay = useIdentityStore((s) => s.currentDay);
const habits = useHabitStore((s) => s.habits);
const habitLogs = useHabitStore((s) => s.habitLogs);
const [mood, setMood] = useState('');
const [win, setWin] = useState('');
const [struggle, setStruggle] = useState('');
const [highlight, setHighlight] = useState('');
const [note, setNote] = useState('');
const [identityCheck, setIdentityCheck] = useState('');
const [saving, setSaving] = useState(false);
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start();
}, []);
const habitsCompleted = Object.values(habitLogs).filter(Boolean).length;
const handleSave = async () => {
if (!mood) {
showAlert('Mood', 'How are you feeling today?');
return;
}
if (!identityCheck) {
showAlert('Identity Check', 'Did you embody your identity today?');
return;
}
setSaving(true);
try {
// Generate AI reflection
const ai = await generateDailyReflection({ mood, win, struggle, highlight, note });
// Save everything
await saveDailyJournal(identity.id, currentDay, {
identityCheck,
habitsCompleted,
mood,
win,
struggle,
highlight,
note,
aiTitle: ai.title,
aiSummary: ai.summary,
aiQuote: ai.quote,
});
// Navigate to result
navigation.navigate('JournalResult', {
aiTitle: ai.title,
aiSummary: ai.summary,
aiQuote: ai.quote,
source: ai.source,
dayNumber: currentDay,
date: new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }),
mood,
identityTitle: identity?.title,
});
} catch (e) {
console.warn('Journal save error:', e);
showAlert('Error', e?.message || 'Failed to save. Try again.');
} finally {
setSaving(false);
}
};
return (
<ScreenWrapper>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={styles.flex}
>
<Animated.ScrollView
style={[styles.flex, { opacity: fadeAnim }]}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* Header */}
<Text style={styles.title}>Daily Journal</Text>
<Text style={styles.subtitle}>Day {currentDay} How was your day?</Text>
{/* Mood */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>How are you feeling?</Text>
<View style={styles.moodRow}>
{MOODS.map((m) => (
<Pressable
key={m.key}
style={[styles.moodBtn, mood === m.key && styles.moodBtnSelected]}
onPress={() => setMood(m.key)}
>
<Text style={styles.moodEmoji}>{m.emoji}</Text>
<Text style={[styles.moodLabel, mood === m.key && styles.moodLabelSelected]}>
{m.label}
</Text>
</Pressable>
))}
</View>
</View>
{/* Identity Check */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Did you embody "{identity?.title}"?</Text>
<View style={styles.identityRow}>
{IDENTITY_OPTIONS.map((opt) => (
<Pressable
key={opt.key}
style={[
styles.identityBtn,
identityCheck === opt.key && { borderColor: opt.color, backgroundColor: `${opt.color}10` },
]}
onPress={() => setIdentityCheck(opt.key)}
>
<Text style={[
styles.identityLabel,
identityCheck === opt.key && { color: opt.color },
]}>
{opt.label}
</Text>
</Pressable>
))}
</View>
</View>
{/* Habits summary */}
<View style={styles.habitSummary}>
<Text style={styles.habitSummaryText}>
{habitsCompleted}/{habits.length} habits completed today
</Text>
</View>
{/* Journal inputs */}
<View style={styles.section}>
<Input
label="What went well today?"
value={win}
onChangeText={setWin}
placeholder="A small or big win..."
/>
<Input
label="What was difficult?"
value={struggle}
onChangeText={setStruggle}
placeholder="A challenge or struggle..."
/>
<Input
label="Highlight of the day"
value={highlight}
onChangeText={setHighlight}
placeholder="A moment that stood out..."
/>
<Input
label="Reflection"
value={note}
onChangeText={setNote}
placeholder="Anything on your mind..."
multiline
/>
</View>
{/* Save */}
<Button
title={saving ? 'Generating reflection...' : 'Save My Day'}
onPress={handleSave}
loading={saving}
disabled={!mood || !identityCheck}
style={styles.saveBtn}
/>
</Animated.ScrollView>
</KeyboardAvoidingView>
</ScreenWrapper>
);
}
const styles = StyleSheet.create({
flex: { flex: 1 },
scrollContent: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.xl,
paddingBottom: spacing.xxl * 2,
},
title: {
color: colors.text,
fontSize: fonts.sizes.xxl,
fontWeight: fonts.weights.bold,
marginBottom: spacing.xs,
},
subtitle: {
color: colors.textSecondary,
fontSize: fonts.sizes.md,
marginBottom: spacing.xl,
},
section: {
marginBottom: spacing.lg,
},
sectionTitle: {
color: colors.text,
fontSize: fonts.sizes.md,
fontWeight: fonts.weights.semibold,
marginBottom: spacing.md,
},
// Mood
moodRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
moodBtn: {
alignItems: 'center',
paddingVertical: spacing.sm,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
backgroundColor: colors.surface,
flex: 1,
marginHorizontal: 3,
},
moodBtnSelected: {
borderColor: colors.primary,
backgroundColor: 'rgba(108, 99, 255, 0.1)',
},
moodEmoji: { fontSize: 22, marginBottom: 4 },
moodLabel: { color: colors.textMuted, fontSize: 10, fontWeight: fonts.weights.medium },
moodLabelSelected: { color: colors.primary },
// Identity check
identityRow: {
flexDirection: 'row',
gap: spacing.sm,
},
identityBtn: {
flex: 1,
alignItems: 'center',
paddingVertical: spacing.md,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
backgroundColor: colors.surface,
},
identityLabel: {
color: colors.textSecondary,
fontSize: fonts.sizes.sm,
fontWeight: fonts.weights.medium,
},
// Habits summary
habitSummary: {
backgroundColor: colors.surface,
borderRadius: borderRadius.md,
padding: spacing.md,
borderWidth: 1,
borderColor: colors.border,
marginBottom: spacing.lg,
alignItems: 'center',
},
habitSummaryText: {
color: colors.textSecondary,
fontSize: fonts.sizes.sm,
},
saveBtn: {
marginTop: spacing.md,
},
});