init
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user