This commit is contained in:
2026-04-21 18:46:41 +02:00
parent 99ed8bae59
commit cb7555590f
6 changed files with 265 additions and 110 deletions

View File

@@ -7,6 +7,7 @@ import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import "react-native-reanimated";
import { BirthdaysProvider } from "@/context/birthdays-context";
import { useColorScheme } from "@/hooks/use-color-scheme";
export const unstable_settings = {
@@ -17,6 +18,7 @@ export default function RootLayout() {
const colorScheme = useColorScheme();
return (
<BirthdaysProvider>
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
@@ -24,5 +26,6 @@ export default function RootLayout() {
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
</BirthdaysProvider>
);
}

View File

@@ -1,25 +1,33 @@
import DateTimePicker from "@react-native-community/datetimepicker";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import { useBirthdays } from "@/context/birthdays-context";
import { useRouter } from "expo-router";
import React, { useState } from "react";
import {
Button,
Alert,
KeyboardAvoidingView,
Platform,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function SimpleForm() {
const router = useRouter();
const { addBirthday } = useBirthdays();
const [name, setName] = useState("");
const [date, setDate] = useState(new Date());
const [showPicker, setShowPicker] = useState(false);
const onChangeDate = (event, selectedDate) => {
const onChangeDate = (
event: DateTimePickerEvent,
selectedDate?: Date
) => {
setShowPicker(Platform.OS === "ios");
if (selectedDate) {
setDate(selectedDate);
@@ -27,8 +35,18 @@ export default function SimpleForm() {
};
const handleSubmit = () => {
alert(`Name: ${name}\nDate: ${date.toDateString()}`);
router.push("/");
if (!name.trim()) {
Alert.alert("Missing name", "Enter a name before saving.");
return;
}
addBirthday({
name,
date: formatBirthdayDate(date),
});
setName("");
setDate(new Date());
router.back();
};
return (
@@ -37,6 +55,8 @@ export default function SimpleForm() {
style={styles.keyboardContainer}
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
<View style={styles.content}>
<Text style={styles.title}>Add Birthday</Text>
<ScrollView
contentContainerStyle={styles.scrollContent}
keyboardShouldPersistTaps="handled"
@@ -45,15 +65,24 @@ export default function SimpleForm() {
<Text style={styles.label}>Name:</Text>
<TextInput
style={styles.input}
placeholder="Enter your name"
placeholder="Enter name of the person"
value={name}
onChangeText={setName}
/>
<Text style={styles.label}>Date:</Text>
<Button title="Select Date" onPress={() => setShowPicker(true)} />
<TouchableOpacity
onPress={() => {
setShowPicker(true);
}}
style={[{ backgroundColor: "#00adaa" }, styles.button]}
>
<Text style={styles.buttonText}>Select Date</Text>
</TouchableOpacity>
<Text style={styles.dateText}>{date.toDateString()}</Text>
<Text style={styles.dateText}>
{"Selected date: " + date.toDateString()}
</Text>
{showPicker && (
<DateTimePicker
@@ -64,9 +93,15 @@ export default function SimpleForm() {
/>
)}
<Button title="Submit" onPress={handleSubmit} />
<TouchableOpacity
onPress={handleSubmit}
style={[{ backgroundColor: "#00984c" }, styles.button]}
>
<Text style={styles.buttonText}>Save</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
@@ -76,30 +111,63 @@ const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#fff",
justifyContent: "center",
marginBottom: 0,
},
keyboardContainer: {
flex: 1,
},
content: {
flex: 1,
paddingHorizontal: 16,
paddingTop: 12,
},
title: {
fontSize: 24,
fontWeight: "bold",
paddingHorizontal: 5,
paddingBottom: 10,
},
scrollContent: {
flexGrow: 1,
justifyContent: "center",
padding: 20,
paddingHorizontal: 20,
paddingBottom: 20,
},
formCard: {
gap: 12,
},
label: {
paddingTop: 20,
fontSize: 16,
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 10,
marginBottom: 15,
borderRadius: 5,
},
dateText: {
fontSize: 16,
marginBottom: 8,
},
button: {
borderRadius: 10,
paddingVertical: 10,
paddingHorizontal: 12,
elevation: 8
},
buttonText: {
fontSize: 18,
color: "#fff",
fontWeight: "bold",
alignSelf: "center"
}
});
function formatBirthdayDate(date: Date) {
const year = date.getFullYear();
const month = `${date.getMonth() + 1}`.padStart(2, "0");
const day = `${date.getDate()}`.padStart(2, "0");
return `${year}-${month}-${day}`;
}

View File

@@ -1,12 +1,25 @@
import { StyleSheet, View } from "react-native";
import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { BirthdayList } from "@/components/birthdate-list";
import { useRouter } from "expo-router";
export default function HomeScreen() {
const router = useRouter();
return (
<SafeAreaView style={styles.screen} edges={["top", "bottom"]}>
<View style={styles.content}>
<View style={styles.actionsContainer}>
<Text style={styles.title}>Upcoming Birthdays</Text>
<TouchableOpacity
onPress={() => router.push("/add")}
style={styles.addButton}
>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
<BirthdayList />
</View>
</SafeAreaView>
@@ -23,4 +36,30 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
paddingTop: 12,
},
title: {
fontSize: 24,
fontWeight: "bold",
paddingHorizontal: 5,
paddingBottom: 10,
},
addButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: "#007AFF",
justifyContent: "center",
alignItems: "center",
},
addButtonText: {
fontSize: 24,
color: "white",
fontWeight: "bold",
},
actionsContainer: {
flexDirection: "row",
justifyContent: "space-between",
gap: 12,
paddingHorizontal: 5,
paddingBottom: 10,
},
});

View File

@@ -21,7 +21,7 @@ const styles = StyleSheet.create({
padding: 15,
borderRadius: 8,
backgroundColor: "#f0f0f0",
boxShadow: "0 1px 4px rgba(0,0,0,0.3)",
elevation: 2,
marginBottom: 10,
},
name: {

View File

@@ -1,60 +1,23 @@
// birthdate-list.tsx
import { useRouter } from "expo-router";
import {
SectionList,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import { BirthdayEntry, useBirthdays } from "@/context/birthdays-context";
import { BirthdayItem } from "./birthdate-item";
const DATA = [
{ id: "1", name: "John Doe", date: "2024-01-15" },
{ id: "2", name: "Jane Smith", date: "2024-01-15" },
{ id: "3", name: "Bob Johnson", date: "2024-02-10" },
{ id: "4", name: "Alice Brown", date: "2024-02-10" },
{ id: "5", name: "Charlie Wilson", date: "2024-01-20" },
{ id: "6", name: "Bob Johnson", date: "2024-02-10" },
{ id: "7", name: "Alice Brown", date: "2024-02-10" },
{ id: "8", name: "Charlie Wilson", date: "2024-01-20" },
{ id: "9", name: "Bob Johnson", date: "2024-02-10" },
{ id: "10", name: "Alice Brown", date: "2024-02-10" },
{ id: "11", name: "Charlie Wilson", date: "2024-01-20" },
{ id: "12", name: "Charlie Wilson", date: "2024-01-20" },
{ id: "13", name: "Charlie Wilson", date: "2024-01-20" },
{ id: "14", name: "Charlie Wilson", date: "2024-01-20" },
];
interface BirthdayItemData {
id: string;
name: string;
date: string;
}
interface SectionData {
title: string;
data: BirthdayItemData[];
data: BirthdayEntry[];
}
export function BirthdayList() {
const router = useRouter();
const groupedData = groupBirthdaysByDate(DATA);
const { birthdays } = useBirthdays();
const groupedData = groupBirthdaysByDate(birthdays);
return (
<View style={styles.container}>
<View style={styles.titleContainer}>
<Text style={{ fontSize: 24, fontWeight: "bold" }}>
Upcoming Birthdays
</Text>
<TouchableOpacity
onPress={() => router.push("/add")}
style={styles.addButton}
>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
<SectionList
sections={groupedData}
keyExtractor={(item) => item.id}
@@ -64,9 +27,17 @@ export function BirthdayList() {
style={styles.list}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<View style={styles.emptyState}>
<Text style={styles.emptyTitle}>No birthdays yet</Text>
<Text style={styles.emptyText}>
Add one from the form and it will appear here.
</Text>
</View>
}
renderSectionHeader={({ section: { title } }) => (
<View style={styles.headerContainer}>
<Text style={styles.sectionHeader}>{title}</Text>
<Text style={styles.sectionHeader}>{formatDisplayDate(title)}</Text>
</View>
)}
/>
@@ -75,10 +46,10 @@ export function BirthdayList() {
}
// Helper function to group and sort
function groupBirthdaysByDate(data: BirthdayItemData[]): SectionData[] {
function groupBirthdaysByDate(data: BirthdayEntry[]): SectionData[] {
// Sort by date
const sorted = [...data].sort((a, b) => {
return new Date(a.date).getTime() - new Date(b.date).getTime();
return parseBirthdayDate(a.date).getTime() - parseBirthdayDate(b.date).getTime();
});
// Group by date
@@ -97,37 +68,28 @@ function groupBirthdaysByDate(data: BirthdayItemData[]): SectionData[] {
return grouped;
}
function parseBirthdayDate(date: string) {
return new Date(`${date}T00:00:00`);
}
function formatDisplayDate(date: string) {
return parseBirthdayDate(date).toLocaleDateString(undefined, {
month: "long",
day: "numeric",
year: "numeric",
});
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
titleContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
gap: 12,
paddingHorizontal: 5,
paddingVertical: 10,
},
list: {
flex: 1,
},
listContent: {
paddingBottom: 20,
},
addButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: "#007AFF",
justifyContent: "center",
alignItems: "center",
},
addButtonText: {
fontSize: 24,
color: "white",
fontWeight: "bold",
},
headerContainer: {
paddingVertical: 10,
paddingHorizontal: 5,
@@ -136,4 +98,17 @@ const styles = StyleSheet.create({
fontSize: 18,
fontWeight: "bold",
},
emptyState: {
paddingHorizontal: 5,
paddingTop: 24,
gap: 6,
},
emptyTitle: {
fontSize: 18,
fontWeight: "bold",
},
emptyText: {
color: "#666",
fontSize: 15,
},
});

View File

@@ -0,0 +1,70 @@
import {
createContext,
ReactNode,
useContext,
useMemo,
useState,
} from "react";
export interface BirthdayEntry {
id: string;
name: string;
date: string;
}
interface AddBirthdayInput {
name: string;
date: string;
}
interface BirthdaysContextValue {
birthdays: BirthdayEntry[];
addBirthday: (input: AddBirthdayInput) => void;
}
const BirthdaysContext = createContext<BirthdaysContextValue | undefined>(
undefined
);
export function BirthdaysProvider({ children }: { children: ReactNode }) {
const [birthdays, setBirthdays] = useState<BirthdayEntry[]>([]);
const value = useMemo(
() => ({
birthdays,
addBirthday: ({ name, date }: AddBirthdayInput) => {
const trimmedName = name.trim();
if (!trimmedName) {
return;
}
setBirthdays((currentBirthdays) => [
...currentBirthdays,
{
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
name: trimmedName,
date,
},
]);
},
}),
[birthdays]
);
return (
<BirthdaysContext.Provider value={value}>
{children}
</BirthdaysContext.Provider>
);
}
export function useBirthdays() {
const context = useContext(BirthdaysContext);
if (!context) {
throw new Error("useBirthdays must be used within a BirthdaysProvider");
}
return context;
}