diff --git a/apk/app/_layout.tsx b/apk/app/_layout.tsx
index 676e7e5..efab7b9 100644
--- a/apk/app/_layout.tsx
+++ b/apk/app/_layout.tsx
@@ -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,12 +18,14 @@ export default function RootLayout() {
const colorScheme = useColorScheme();
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apk/app/add.tsx b/apk/app/add.tsx
index 6ff0d33..3547f70 100644
--- a/apk/app/add.tsx
+++ b/apk/app/add.tsx
@@ -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,36 +55,53 @@ export default function SimpleForm() {
style={styles.keyboardContainer}
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
-
-
- Name:
-
-
- Date:
-
+
+
);
@@ -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}`;
+}
diff --git a/apk/app/index.tsx b/apk/app/index.tsx
index 8daea7d..417f39e 100644
--- a/apk/app/index.tsx
+++ b/apk/app/index.tsx
@@ -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 (
+
+ Upcoming Birthdays
+ router.push("/add")}
+ style={styles.addButton}
+ >
+ +
+
+
@@ -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,
+ },
});
diff --git a/apk/components/birthdate-item.tsx b/apk/components/birthdate-item.tsx
index 5512e3d..a7a4e7d 100644
--- a/apk/components/birthdate-item.tsx
+++ b/apk/components/birthdate-item.tsx
@@ -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: {
diff --git a/apk/components/birthdate-list.tsx b/apk/components/birthdate-list.tsx
index 1233ddc..34b19de 100644
--- a/apk/components/birthdate-list.tsx
+++ b/apk/components/birthdate-list.tsx
@@ -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 (
-
-
- Upcoming Birthdays
-
- router.push("/add")}
- style={styles.addButton}
- >
- +
-
-
-
item.id}
@@ -64,9 +27,17 @@ export function BirthdayList() {
style={styles.list}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
+ ListEmptyComponent={
+
+ No birthdays yet
+
+ Add one from the form and it will appear here.
+
+
+ }
renderSectionHeader={({ section: { title } }) => (
- {title}
+ {formatDisplayDate(title)}
)}
/>
@@ -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,
+ },
});
diff --git a/apk/context/birthdays-context.tsx b/apk/context/birthdays-context.tsx
new file mode 100644
index 0000000..3286e8c
--- /dev/null
+++ b/apk/context/birthdays-context.tsx
@@ -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(
+ undefined
+);
+
+export function BirthdaysProvider({ children }: { children: ReactNode }) {
+ const [birthdays, setBirthdays] = useState([]);
+
+ 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 (
+
+ {children}
+
+ );
+}
+
+export function useBirthdays() {
+ const context = useContext(BirthdaysContext);
+
+ if (!context) {
+ throw new Error("useBirthdays must be used within a BirthdaysProvider");
+ }
+
+ return context;
+}