299 lines
8.9 KiB
JavaScript
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,
|
|
},
|
|
});
|