Kantongku: onboarding bersih - sambutan + poin fitur (tanpa form kategori)

This commit is contained in:
Bro
2026-08-16 16:01:26 +07:00
parent 6c9276c7fa
commit c5a853ae94
+54 -203
View File
@@ -2,55 +2,38 @@ import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useState } from 'react';
import {
Alert,
Image,
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { colors } from '../theme/colors';
import {
addCategory,
getCategories,
setSetting,
} from '../db/repositories';
import type { Category, TransactionType } from '../types';
import { setSetting } from '../db/repositories';
import type { RootStackParamList } from '../navigation';
const QUICK_EMOJIS = ['🍜', '🚌', '🛒', '🧾', '💊', '🎬', '💼', '🎁', '🐱', '📱', '🏠', '🎮'];
const FEATURES = [
{
icon: 'create-outline' as const,
title: 'Catat dengan cepat',
desc: 'Pemasukan & pengeluaran, langsung terformat rupiah.',
},
{
icon: 'lock-closed-outline' as const,
title: 'Auto-sisih otomatis',
desc: '10% dari setiap pemasukan langsung masuk tabungan.',
},
{
icon: 'phone-portrait-outline' as const,
title: 'Offline & privat',
desc: 'Semua data tersimpan di HP-mu. Tanpa server, tanpa internet.',
},
{
icon: 'trophy-outline' as const,
title: 'Tetap semangat',
desc: 'Target, badge, dan streak yang bikin kamu terus menabung.',
},
];
export function OnboardingScreen() {
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const [type, setType] = useState<TransactionType>('expense');
const [name, setName] = useState('');
const [icon, setIcon] = useState('🍜');
const [categories, setCategories] = useState<Category[]>([]);
const [loading, setLoading] = useState(true);
// Muat kategori yang sudah ada (default)
useState(() => {
getCategories().then((c) => {
setCategories(c);
setLoading(false);
});
});
const handleAdd = async () => {
if (!name.trim()) {
Alert.alert('Nama kosong', 'Kasih nama kategorinya dulu ya.');
return;
}
await addCategory({ name: name.trim(), type, icon });
setName('');
setCategories(await getCategories());
};
const handleStart = async () => {
await setSetting('onboarded', '1');
@@ -73,92 +56,25 @@ export function OnboardingScreen() {
style={styles.logo}
resizeMode="contain"
/>
<Text style={styles.heroTitle}>Selamat datang di Kantongku! 👛</Text>
<Text style={styles.heroTitle}>Kantongku</Text>
<Text style={styles.heroSub}>
Catat pemasukan, sisihkan otomatis, dan jaga semangat nabungmu —
semua offline, semua di perangkatmu.
Asisten keuangan pribadi yang bikin kamu semangat menabung.
</Text>
</LinearGradient>
<Text style={styles.sectionTitle}>
Tambah kategori favoritmu (opsional)
</Text>
<Text style={styles.sectionHint}>
Misal: Kucing 🐱, Gadget 📱 — biar pencatatanmu makin personal.
</Text>
{/* Toggle tipe */}
<View style={styles.toggleRow}>
<Pressable
style={[styles.toggleBtn, type === 'income' && styles.toggleIncome]}
onPress={() => setType('income')}
>
<Text
style={[
styles.toggleText,
type === 'income' && { color: colors.income, fontWeight: '700' },
]}
>
+ Pemasukan
</Text>
</Pressable>
<Pressable
style={[styles.toggleBtn, type === 'expense' && styles.toggleExpense]}
onPress={() => setType('expense')}
>
<Text
style={[
styles.toggleText,
type === 'expense' && { color: colors.expense, fontWeight: '700' },
]}
>
- Pengeluaran
</Text>
</Pressable>
<View style={styles.featureList}>
{FEATURES.map((f) => (
<View key={f.title} style={styles.featureRow}>
<View style={styles.featureIcon}>
<Ionicons name={f.icon} size={20} color={colors.primaryDark} />
</View>
<TextInput
style={[styles.card, styles.input]}
value={name}
onChangeText={setName}
placeholder="Nama kategori (misal: Kucing)"
placeholderTextColor={colors.textMuted}
/>
<View style={styles.emojiGrid}>
{QUICK_EMOJIS.map((e) => (
<Pressable
key={e}
style={[styles.emojiChip, icon === e && styles.emojiChipActive]}
onPress={() => setIcon(e)}
>
<Text style={styles.emojiText}>{e}</Text>
</Pressable>
))}
<View style={styles.featureInfo}>
<Text style={styles.featureTitle}>{f.title}</Text>
<Text style={styles.featureDesc}>{f.desc}</Text>
</View>
<Pressable
style={({ pressed }) => [styles.addBtn, pressed && styles.pressed]}
onPress={handleAdd}
>
<Ionicons name="add" size={18} color={colors.white} />
<Text style={styles.addBtnText}>Tambah Kategori</Text>
</Pressable>
{!loading && categories.length > 0 && (
<View style={styles.existingWrap}>
<Text style={styles.existingLabel}>Kategori yang tersedia:</Text>
<View style={styles.chipWrap}>
{categories.map((c) => (
<View key={c.id} style={styles.chip}>
<Text style={styles.chipText}>
{c.icon} {c.name}
</Text>
</View>
))}
</View>
</View>
)}
<Pressable
style={({ pressed }) => [styles.startBtn, pressed && styles.pressed]}
@@ -170,11 +86,11 @@ export function OnboardingScreen() {
end={{ x: 1, y: 1 }}
style={styles.startGradient}
>
<Text style={styles.startText}>🚀 Mulai Pakai Kantongku</Text>
<Text style={styles.startText}>Mulai Pakai Kantongku</Text>
</LinearGradient>
</Pressable>
<Text style={styles.footerNote}>
Kategori bisa diubah kapan saja di menu Pengaturan.
Kategori bisa diatur kapan saja di menu Pengaturan.
</Text>
</ScrollView>
</SafeAreaView>
@@ -188,7 +104,7 @@ const styles = StyleSheet.create({
marginHorizontal: 16,
marginTop: 16,
borderRadius: 24,
padding: 22,
padding: 24,
overflow: 'hidden',
alignItems: 'center',
shadowColor: colors.primaryDark,
@@ -215,110 +131,45 @@ const styles = StyleSheet.create({
borderRadius: 55,
backgroundColor: 'rgba(255,255,255,0.06)',
},
logo: { width: 96, height: 96, marginBottom: 12 },
logo: { width: 84, height: 84, marginBottom: 10 },
heroTitle: {
fontSize: 22,
fontSize: 26,
fontWeight: '800',
color: colors.white,
textAlign: 'center',
},
heroSub: {
fontSize: 13,
color: 'rgba(255,255,255,0.9)',
textAlign: 'center',
lineHeight: 19,
marginTop: 8,
marginTop: 6,
},
sectionTitle: {
fontSize: 16,
fontWeight: '700',
color: colors.text,
marginTop: 20,
marginHorizontal: 16,
},
sectionHint: {
fontSize: 12,
color: colors.textMuted,
marginHorizontal: 16,
marginTop: 4,
marginBottom: 10,
},
toggleRow: {
featureList: { marginHorizontal: 16, marginTop: 20, gap: 4 },
featureRow: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.surface,
borderRadius: 14,
borderWidth: 1,
borderColor: colors.border,
padding: 4,
marginHorizontal: 16,
marginBottom: 10,
},
toggleBtn: { flex: 1, alignItems: 'center', paddingVertical: 10, borderRadius: 10 },
toggleIncome: { backgroundColor: colors.incomeSoft },
toggleExpense: { backgroundColor: colors.expenseSoft },
toggleText: { fontSize: 14, color: colors.textMuted },
card: {
backgroundColor: colors.surface,
borderRadius: 16,
padding: 16,
borderWidth: 1,
borderColor: colors.border,
padding: 14,
marginBottom: 8,
},
input: {
marginHorizontal: 16,
paddingVertical: 12,
fontSize: 15,
color: colors.text,
marginBottom: 10,
},
emojiGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
marginHorizontal: 16,
},
emojiChip: {
width: 48,
height: 48,
borderRadius: 13,
backgroundColor: colors.surface,
borderWidth: 1,
borderColor: colors.border,
alignItems: 'center',
justifyContent: 'center',
},
emojiChipActive: {
borderColor: colors.primary,
featureIcon: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: colors.primarySoft,
},
emojiText: { fontSize: 22 },
addBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
backgroundColor: colors.primary,
borderRadius: 12,
paddingVertical: 12,
marginHorizontal: 16,
marginTop: 12,
marginRight: 12,
},
addBtnText: { color: colors.white, fontWeight: '700', fontSize: 14 },
existingWrap: { marginHorizontal: 16, marginTop: 16 },
existingLabel: { fontSize: 12, color: colors.textMuted, marginBottom: 8 },
chipWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 6 },
chip: {
backgroundColor: colors.surface,
borderWidth: 1,
borderColor: colors.border,
borderRadius: 999,
paddingHorizontal: 10,
paddingVertical: 6,
},
chipText: { fontSize: 12, color: colors.text },
featureInfo: { flex: 1 },
featureTitle: { fontSize: 14, fontWeight: '700', color: colors.text },
featureDesc: { fontSize: 12, color: colors.textMuted, marginTop: 2, lineHeight: 17 },
startBtn: {
marginHorizontal: 16,
marginTop: 24,
marginTop: 20,
borderRadius: 14,
shadowColor: colors.primaryDark,
shadowOpacity: 0.25,