diff --git a/CLAUDE.md b/CLAUDE.md
index 78c34df..aa779b7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -39,6 +39,8 @@ npm run start -w @caldav/server # Run compiled server (port 3000)
| | Expo | Development platform |
| | Expo-Router | File-based routing |
| | NativeWind | Tailwind CSS for React Native |
+| | Zustand | State management |
+| | FlashList | High-performance lists |
| Backend | Express.js | Web framework |
| | MongoDB | Database |
| | Mongoose | ODM |
@@ -57,13 +59,39 @@ packages/shared - @caldav/shared - Shared TypeScript types and models
### Frontend Architecture (apps/client)
-**Screens** (`src/app/`): Login, Register, Calendar, Chat, Event Detail, Notes - file-based routing via Expo-Router
+```
+src/
+├── app/ # Expo-Router file-based routing
+│ ├── _layout.tsx # Root Stack layout
+│ ├── index.tsx # Entry redirect
+│ ├── login.tsx # Login screen
+│ ├── register.tsx # Registration screen
+│ ├── (tabs)/ # Tab navigation group
+│ │ ├── _layout.tsx # Tab bar configuration
+│ │ ├── chat.tsx # Chat screen (AI conversation)
+│ │ └── calendar.tsx # Calendar overview
+│ ├── event/
+│ │ └── [id].tsx # Event detail screen (dynamic route)
+│ └── note/
+│ └── [id].tsx # Note editor for event (dynamic route)
+├── components/
+│ ├── BaseBackground.tsx # Common screen wrapper
+│ ├── Header.tsx # Header component
+│ ├── EventCard.tsx # Event card for calendar display
+│ └── EventConfirmDialog.tsx # AI-proposed event confirmation modal
+├── services/
+│ ├── index.ts # Re-exports all services
+│ ├── ApiClient.ts # HTTP client (get, post, put, delete)
+│ ├── AuthService.ts # login(), register(), logout(), refresh()
+│ ├── EventService.ts # getAll(), getById(), getByDateRange(), create(), update(), delete()
+│ └── ChatService.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation()
+└── stores/ # Zustand state management
+ ├── index.ts # Re-exports all stores
+ ├── AuthStore.ts # user, token, isAuthenticated, login(), logout(), setToken()
+ └── EventsStore.ts # events[], setEvents(), addEvent(), updateEvent(), deleteEvent()
+```
-**Services**: API Client for HTTP requests, plus AuthService, EventService, ChatService for domain logic
-
-**Components**: Reusable UI (EventCard, ChatBubble, EventConfirmDialog, MonthSelector)
-
-**Stores**: AuthStore (user + token), EventsStore (calendar events)
+**Routing:** Tab-based navigation with Chat and Calendar as main screens. Auth screens (login, register) outside tabs. Dynamic routes for event detail and note editing.
### Backend Architecture (apps/server)
@@ -176,7 +204,14 @@ The repository pattern allows swapping databases:
**Shared:** Types and DTOs defined and exported.
-**Frontend:** Calendar screen partially implemented, Chat screen exists but not connected to backend.
+**Frontend:** Skeleton complete with file-based routing structure:
+- Tab navigation (Chat, Calendar) implemented with basic UI
+- Calendar screen has month navigation and grid display (partially functional)
+- Chat screen has message list UI with FlashList (mock data only)
+- Auth screens (Login, Register), Event Detail, and Note screens exist as skeletons
+- Services (ApiClient, AuthService, EventService, ChatService) defined with `throw new Error('Not implemented')`
+- Zustand stores (AuthStore, EventsStore) defined with `throw new Error('Not implemented')`
+- Components (EventCard, EventConfirmDialog) exist as skeletons
## Documentation
diff --git a/apps/client/package.json b/apps/client/package.json
index ec468c6..b87bb6e 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -38,7 +38,8 @@
"react-native-safe-area-context": "5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
- "react-native-worklets": "0.5.1"
+ "react-native-worklets": "0.5.1",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@types/react": "~19.1.0",
diff --git a/apps/client/src/app/(tabs)/_layout.tsx b/apps/client/src/app/(tabs)/_layout.tsx
new file mode 100644
index 0000000..378e1ac
--- /dev/null
+++ b/apps/client/src/app/(tabs)/_layout.tsx
@@ -0,0 +1,29 @@
+import { Ionicons } from "@expo/vector-icons";
+import { Tabs } from "expo-router";
+import theme from "../../Themes";
+
+export default function TabLayout() {
+ return (
+
+ ,
+ }}
+ />
+ ,
+ }}
+ />
+
+ );
+}
diff --git a/apps/client/src/app/Calender.tsx b/apps/client/src/app/(tabs)/calendar.tsx
similarity index 97%
rename from apps/client/src/app/Calender.tsx
rename to apps/client/src/app/(tabs)/calendar.tsx
index 5fe108a..7a7603d 100644
--- a/apps/client/src/app/Calender.tsx
+++ b/apps/client/src/app/(tabs)/calendar.tsx
@@ -1,9 +1,9 @@
import { Animated, Modal, Pressable, Text, View } from "react-native";
-import { DAYS, MONTHS, Month } from "../Constants";
-import Header from "../components/Header";
+import { DAYS, MONTHS, Month } from "../../Constants";
+import Header from "../../components/Header";
import React, { useEffect, useMemo, useRef, useState } from "react";
-import currentTheme from "../Themes";
-import BaseBackground from "../components/BaseBackground";
+import currentTheme from "../../Themes";
+import BaseBackground from "../../components/BaseBackground";
import { FlashList } from "@shopify/flash-list";
// TODO: month selection dropdown menu
diff --git a/apps/client/src/app/Chat.tsx b/apps/client/src/app/(tabs)/chat.tsx
similarity index 97%
rename from apps/client/src/app/Chat.tsx
rename to apps/client/src/app/(tabs)/chat.tsx
index ffcf803..ff4684c 100644
--- a/apps/client/src/app/Chat.tsx
+++ b/apps/client/src/app/(tabs)/chat.tsx
@@ -1,8 +1,8 @@
import { View, Text, TextInput } from "react-native";
-import currentTheme from "../Themes";
+import currentTheme from "../../Themes";
import { useState } from "react";
-import Header from "../components/Header";
-import BaseBackground from "../components/BaseBackground";
+import Header from "../../components/Header";
+import BaseBackground from "../../components/BaseBackground";
import { FlashList } from "@shopify/flash-list";
// TODO: better shadows for everything
diff --git a/apps/client/src/app/Hello_World.tsx b/apps/client/src/app/Hello_World.tsx
deleted file mode 100644
index f9e177d..0000000
--- a/apps/client/src/app/Hello_World.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { useState } from 'react';
-import { Button, Text, View } from 'react-native';
-
-// const styles = StyleSheet.create({
-// container: {
-// alignItems: 'center',
-// },
-// text: {
-// fontSize: 40
-// }
-// })
-
-type HelloWorldProps = {
- text: string;
- aNumber: number;
-}
-
-const HelloWorld = (props: HelloWorldProps) => {
- return (
-
- {props.text} : {props.aNumber}
-
- )
-}
-
-const Counter = () => {
- const [count, setCount] = useState(0);
-
- return (
-
-
- )
-}
-
-const ManyHelloes = () => {
- return (
-
-
-
-
-
-
- )
-}
-
-export default ManyHelloes;
diff --git a/apps/client/src/app/_layout.tsx b/apps/client/src/app/_layout.tsx
index 9a78701..fb120f2 100644
--- a/apps/client/src/app/_layout.tsx
+++ b/apps/client/src/app/_layout.tsx
@@ -2,5 +2,13 @@ import { Stack } from "expo-router";
import "../../global.css";
export default function RootLayout() {
- return ;
+ return (
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/client/src/app/event/[id].tsx b/apps/client/src/app/event/[id].tsx
new file mode 100644
index 0000000..2fc2feb
--- /dev/null
+++ b/apps/client/src/app/event/[id].tsx
@@ -0,0 +1,44 @@
+import { View, Text, TextInput, Pressable } from 'react-native';
+import { useLocalSearchParams } from 'expo-router';
+import BaseBackground from '../../components/BaseBackground';
+
+const EventDetailScreen = () => {
+ const { id } = useLocalSearchParams<{ id: string }>();
+
+ // TODO: Fetch event by id using EventService.getById()
+ // TODO: Display event details (title, description, start/end time)
+ // TODO: Edit mode toggle
+ // TODO: Save changes -> EventService.update()
+ // TODO: Delete button -> EventService.delete()
+ // TODO: Link to NoteScreen for this event
+ // TODO: Loading and error states
+ throw new Error('Not implemented');
+
+ return (
+
+
+ Event Detail
+ ID: {id}
+
+
+
+
+ Save
+
+
+ Delete
+
+
+
+
+ );
+};
+
+export default EventDetailScreen;
diff --git a/apps/client/src/app/index.tsx b/apps/client/src/app/index.tsx
index 691c873..4b14027 100644
--- a/apps/client/src/app/index.tsx
+++ b/apps/client/src/app/index.tsx
@@ -1,10 +1,5 @@
-import React from "react";
-import Chat from "./Chat";
-import Calender from "./Calender";
+import { Redirect } from "expo-router";
export default function Index() {
- return (
- //
-
- );
+ return ;
}
diff --git a/apps/client/src/app/login.tsx b/apps/client/src/app/login.tsx
new file mode 100644
index 0000000..994dda5
--- /dev/null
+++ b/apps/client/src/app/login.tsx
@@ -0,0 +1,34 @@
+import { View, Text, TextInput, Pressable } from 'react-native';
+import BaseBackground from '../components/BaseBackground';
+
+const LoginScreen = () => {
+ // TODO: Email input field
+ // TODO: Password input field
+ // TODO: Login button -> AuthService.login()
+ // TODO: Link to RegisterScreen
+ // TODO: Error handling and display
+ // TODO: Navigate to Calendar on success
+ throw new Error('Not implemented');
+
+ return (
+
+
+ Login
+
+
+
+ Login
+
+
+
+ );
+};
+
+export default LoginScreen;
diff --git a/apps/client/src/app/note/[id].tsx b/apps/client/src/app/note/[id].tsx
new file mode 100644
index 0000000..3c62e77
--- /dev/null
+++ b/apps/client/src/app/note/[id].tsx
@@ -0,0 +1,34 @@
+import { View, Text, TextInput, Pressable } from 'react-native';
+import { useLocalSearchParams } from 'expo-router';
+import BaseBackground from '../../components/BaseBackground';
+
+const NoteScreen = () => {
+ const { id } = useLocalSearchParams<{ id: string }>();
+
+ // TODO: Fetch event by id using EventService.getById()
+ // TODO: Display and edit the event's note field
+ // TODO: Auto-save or manual save button
+ // TODO: Save changes -> EventService.update({ note: ... })
+ // TODO: Loading and error states
+ throw new Error('Not implemented');
+
+ return (
+
+
+ Note
+ Event ID: {id}
+
+
+ Save Note
+
+
+
+ );
+};
+
+export default NoteScreen;
diff --git a/apps/client/src/app/register.tsx b/apps/client/src/app/register.tsx
new file mode 100644
index 0000000..3525e46
--- /dev/null
+++ b/apps/client/src/app/register.tsx
@@ -0,0 +1,45 @@
+import { View, Text, TextInput, Pressable } from 'react-native';
+import BaseBackground from '../components/BaseBackground';
+
+const RegisterScreen = () => {
+ // TODO: Email input field
+ // TODO: Display name input field
+ // TODO: Password input field
+ // TODO: Password confirmation field
+ // TODO: Register button -> AuthService.register()
+ // TODO: Link to LoginScreen
+ // TODO: Error handling and display
+ // TODO: Navigate to Calendar on success
+ throw new Error('Not implemented');
+
+ return (
+
+
+ Register
+
+
+
+
+
+ Register
+
+
+
+ );
+};
+
+export default RegisterScreen;
diff --git a/apps/client/src/components/EventCard.tsx b/apps/client/src/components/EventCard.tsx
new file mode 100644
index 0000000..d05c156
--- /dev/null
+++ b/apps/client/src/components/EventCard.tsx
@@ -0,0 +1,24 @@
+import { View, Text, Pressable } from 'react-native';
+import { CalendarEvent } from '@caldav/shared';
+
+type EventCardProps = {
+ event: CalendarEvent;
+ onPress?: (event: CalendarEvent) => void;
+};
+
+const EventCard = ({ event: _event, onPress: _onPress }: EventCardProps) => {
+ // TODO: Display event title, time, and description preview
+ // TODO: Handle onPress to navigate to EventDetailScreen
+ // TODO: Style based on event type or time of day
+ throw new Error('Not implemented');
+
+ return (
+
+
+ EventCard - Not Implemented
+
+
+ );
+};
+
+export default EventCard;
diff --git a/apps/client/src/components/EventConfirmDialog.tsx b/apps/client/src/components/EventConfirmDialog.tsx
new file mode 100644
index 0000000..f9ce6f3
--- /dev/null
+++ b/apps/client/src/components/EventConfirmDialog.tsx
@@ -0,0 +1,36 @@
+import { View, Text, Modal, Pressable } from 'react-native';
+import { CreateEventDTO } from '@caldav/shared';
+
+type EventConfirmDialogProps = {
+ visible: boolean;
+ proposedEvent: CreateEventDTO | null;
+ onConfirm: () => void;
+ onReject: () => void;
+ onClose: () => void;
+};
+
+const EventConfirmDialog = ({
+ visible: _visible,
+ proposedEvent: _proposedEvent,
+ onConfirm: _onConfirm,
+ onReject: _onReject,
+ onClose: _onClose,
+}: EventConfirmDialogProps) => {
+ // TODO: Display proposed event details (title, time, description)
+ // TODO: Confirm button calls onConfirm and closes dialog
+ // TODO: Reject button calls onReject and closes dialog
+ // TODO: Close button or backdrop tap calls onClose
+ throw new Error('Not implemented');
+
+ return (
+
+
+
+ EventConfirmDialog - Not Implemented
+
+
+
+ );
+};
+
+export default EventConfirmDialog;
diff --git a/apps/client/src/services/ApiClient.ts b/apps/client/src/services/ApiClient.ts
new file mode 100644
index 0000000..19e56fe
--- /dev/null
+++ b/apps/client/src/services/ApiClient.ts
@@ -0,0 +1,51 @@
+const API_BASE_URL =
+ process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000/api";
+
+type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
+
+interface RequestOptions {
+ headers?: Record;
+ body?: unknown;
+}
+
+async function request(
+ _method: HttpMethod,
+ _endpoint: string,
+ _options?: RequestOptions,
+): Promise {
+ throw new Error("Not implemented");
+}
+
+export const ApiClient = {
+ get: async (
+ endpoint: string,
+ options?: Omit,
+ ): Promise => {
+ return request("GET", endpoint, options);
+ },
+
+ post: async (
+ endpoint: string,
+ body?: unknown,
+ options?: RequestOptions,
+ ): Promise => {
+ return request("POST", endpoint, { ...options, body });
+ },
+
+ put: async (
+ endpoint: string,
+ body?: unknown,
+ options?: RequestOptions,
+ ): Promise => {
+ return request("PUT", endpoint, { ...options, body });
+ },
+
+ delete: async (
+ endpoint: string,
+ options?: Omit,
+ ): Promise => {
+ return request("DELETE", endpoint, options);
+ },
+};
+
+export { API_BASE_URL };
diff --git a/apps/client/src/services/AuthService.ts b/apps/client/src/services/AuthService.ts
new file mode 100644
index 0000000..ec79fbe
--- /dev/null
+++ b/apps/client/src/services/AuthService.ts
@@ -0,0 +1,19 @@
+import { LoginDTO, CreateUserDTO, AuthResponse } from '@caldav/shared';
+
+export const AuthService = {
+ login: async (_credentials: LoginDTO): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ register: async (_data: CreateUserDTO): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ logout: async (): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ refresh: async (): Promise => {
+ throw new Error('Not implemented');
+ },
+};
diff --git a/apps/client/src/services/ChatService.ts b/apps/client/src/services/ChatService.ts
new file mode 100644
index 0000000..7dda752
--- /dev/null
+++ b/apps/client/src/services/ChatService.ts
@@ -0,0 +1,39 @@
+import {
+ SendMessageDTO,
+ ChatResponse,
+ ChatMessage,
+ ConversationSummary,
+ GetMessagesOptions,
+ CalendarEvent,
+} from '@caldav/shared';
+
+export const ChatService = {
+ sendMessage: async (_data: SendMessageDTO): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ confirmEvent: async (
+ _conversationId: string,
+ _messageId: string
+ ): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ rejectEvent: async (
+ _conversationId: string,
+ _messageId: string
+ ): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ getConversations: async (): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ getConversation: async (
+ _id: string,
+ _options?: GetMessagesOptions
+ ): Promise => {
+ throw new Error('Not implemented');
+ },
+};
diff --git a/apps/client/src/services/EventService.ts b/apps/client/src/services/EventService.ts
new file mode 100644
index 0000000..0105489
--- /dev/null
+++ b/apps/client/src/services/EventService.ts
@@ -0,0 +1,27 @@
+import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
+
+export const EventService = {
+ getAll: async (): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ getById: async (_id: string): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ getByDateRange: async (_start: Date, _end: Date): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ create: async (_data: CreateEventDTO): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ update: async (_id: string, _data: UpdateEventDTO): Promise => {
+ throw new Error('Not implemented');
+ },
+
+ delete: async (_id: string): Promise => {
+ throw new Error('Not implemented');
+ },
+};
diff --git a/apps/client/src/services/index.ts b/apps/client/src/services/index.ts
new file mode 100644
index 0000000..8556f4c
--- /dev/null
+++ b/apps/client/src/services/index.ts
@@ -0,0 +1,4 @@
+export { ApiClient, API_BASE_URL } from './ApiClient';
+export { AuthService } from './AuthService';
+export { EventService } from './EventService';
+export { ChatService } from './ChatService';
diff --git a/apps/client/src/stores/AuthStore.ts b/apps/client/src/stores/AuthStore.ts
new file mode 100644
index 0000000..2ed4466
--- /dev/null
+++ b/apps/client/src/stores/AuthStore.ts
@@ -0,0 +1,26 @@
+import { create } from 'zustand';
+import { User } from '@caldav/shared';
+
+interface AuthState {
+ user: User | null;
+ token: string | null;
+ isAuthenticated: boolean;
+ login: (user: User, token: string) => void;
+ logout: () => void;
+ setToken: (token: string) => void;
+}
+
+export const useAuthStore = create((set) => ({
+ user: null,
+ token: null,
+ isAuthenticated: false,
+ login: (_user: User, _token: string) => {
+ throw new Error('Not implemented');
+ },
+ logout: () => {
+ throw new Error('Not implemented');
+ },
+ setToken: (_token: string) => {
+ throw new Error('Not implemented');
+ },
+}));
diff --git a/apps/client/src/stores/EventsStore.ts b/apps/client/src/stores/EventsStore.ts
new file mode 100644
index 0000000..5664f28
--- /dev/null
+++ b/apps/client/src/stores/EventsStore.ts
@@ -0,0 +1,26 @@
+import { create } from 'zustand';
+import { CalendarEvent } from '@caldav/shared';
+
+interface EventsState {
+ events: CalendarEvent[];
+ setEvents: (events: CalendarEvent[]) => void;
+ addEvent: (event: CalendarEvent) => void;
+ updateEvent: (id: string, event: Partial) => void;
+ deleteEvent: (id: string) => void;
+}
+
+export const useEventsStore = create((set) => ({
+ events: [],
+ setEvents: (_events: CalendarEvent[]) => {
+ throw new Error('Not implemented');
+ },
+ addEvent: (_event: CalendarEvent) => {
+ throw new Error('Not implemented');
+ },
+ updateEvent: (_id: string, _event: Partial) => {
+ throw new Error('Not implemented');
+ },
+ deleteEvent: (_id: string) => {
+ throw new Error('Not implemented');
+ },
+}));
diff --git a/apps/client/src/stores/index.ts b/apps/client/src/stores/index.ts
new file mode 100644
index 0000000..f395794
--- /dev/null
+++ b/apps/client/src/stores/index.ts
@@ -0,0 +1,2 @@
+export { useAuthStore } from './AuthStore';
+export { useEventsStore } from './EventsStore';
diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json
index d4d1254..05c6f3f 100644
--- a/apps/client/tsconfig.json
+++ b/apps/client/tsconfig.json
@@ -2,12 +2,14 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2020",
- "module": "CommonJS",
+ "module": "ESNext",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "jsx": "react-native",
+ "moduleResolution": "bundler"
},
"include": [
"**/*.ts",
diff --git a/package-lock.json b/package-lock.json
index cdfa599..41f93fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,8 @@
"react-native-safe-area-context": "5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
- "react-native-worklets": "0.5.1"
+ "react-native-worklets": "0.5.1",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@types/react": "~19.1.0",
@@ -15412,6 +15413,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/zustand": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
+ "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
"packages/shared": {
"name": "@caldav/shared",
"version": "1.0.0"