implement frontend skeleton with tab navigation and service layer
- Add tab-based navigation (Chat, Calendar) using Expo-Router - Create auth screens (login, register) as skeletons - Add dynamic routes for event detail and note editing - Implement service layer (ApiClient, AuthService, EventService, ChatService) - Add Zustand stores (AuthStore, EventsStore) for state management - Create EventCard and EventConfirmDialog components - Update CLAUDE.md with new frontend architecture documentation - Add Zustand and FlashList to technology stack
This commit is contained in:
49
CLAUDE.md
49
CLAUDE.md
@@ -39,6 +39,8 @@ npm run start -w @caldav/server # Run compiled server (port 3000)
|
|||||||
| | Expo | Development platform |
|
| | Expo | Development platform |
|
||||||
| | Expo-Router | File-based routing |
|
| | Expo-Router | File-based routing |
|
||||||
| | NativeWind | Tailwind CSS for React Native |
|
| | NativeWind | Tailwind CSS for React Native |
|
||||||
|
| | Zustand | State management |
|
||||||
|
| | FlashList | High-performance lists |
|
||||||
| Backend | Express.js | Web framework |
|
| Backend | Express.js | Web framework |
|
||||||
| | MongoDB | Database |
|
| | MongoDB | Database |
|
||||||
| | Mongoose | ODM |
|
| | Mongoose | ODM |
|
||||||
@@ -57,13 +59,39 @@ packages/shared - @caldav/shared - Shared TypeScript types and models
|
|||||||
|
|
||||||
### Frontend Architecture (apps/client)
|
### 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
|
**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.
|
||||||
|
|
||||||
**Components**: Reusable UI (EventCard, ChatBubble, EventConfirmDialog, MonthSelector)
|
|
||||||
|
|
||||||
**Stores**: AuthStore (user + token), EventsStore (calendar events)
|
|
||||||
|
|
||||||
### Backend Architecture (apps/server)
|
### Backend Architecture (apps/server)
|
||||||
|
|
||||||
@@ -176,7 +204,14 @@ The repository pattern allows swapping databases:
|
|||||||
|
|
||||||
**Shared:** Types and DTOs defined and exported.
|
**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
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
"react-native-safe-area-context": "5.6.0",
|
"react-native-safe-area-context": "5.6.0",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
"react-native-worklets": "0.5.1"
|
"react-native-worklets": "0.5.1",
|
||||||
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
|||||||
29
apps/client/src/app/(tabs)/_layout.tsx
Normal file
29
apps/client/src/app/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { Tabs } from "expo-router";
|
||||||
|
import theme from "../../Themes";
|
||||||
|
|
||||||
|
export default function TabLayout() {
|
||||||
|
return (
|
||||||
|
<Tabs screenOptions={{
|
||||||
|
headerShown: false,
|
||||||
|
tabBarActiveTintColor: theme.chatBot,
|
||||||
|
tabBarInactiveTintColor: theme.primeFg,
|
||||||
|
tabBarStyle: { backgroundColor: theme.primeBg },
|
||||||
|
}}>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="chat"
|
||||||
|
options={{
|
||||||
|
title: 'Chat',
|
||||||
|
tabBarIcon: ({ color }) => <Ionicons size={28} name="chatbubble" color={color} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="calendar"
|
||||||
|
options={{
|
||||||
|
title: 'Calendar',
|
||||||
|
tabBarIcon: ({ color }) => <Ionicons size={28} name="calendar" color={color} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Animated, Modal, Pressable, Text, View } from "react-native";
|
import { Animated, Modal, Pressable, Text, View } from "react-native";
|
||||||
import { DAYS, MONTHS, Month } from "../Constants";
|
import { DAYS, MONTHS, Month } from "../../Constants";
|
||||||
import Header from "../components/Header";
|
import Header from "../../components/Header";
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import currentTheme from "../Themes";
|
import currentTheme from "../../Themes";
|
||||||
import BaseBackground from "../components/BaseBackground";
|
import BaseBackground from "../../components/BaseBackground";
|
||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
|
|
||||||
// TODO: month selection dropdown menu
|
// TODO: month selection dropdown menu
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { View, Text, TextInput } from "react-native";
|
import { View, Text, TextInput } from "react-native";
|
||||||
import currentTheme from "../Themes";
|
import currentTheme from "../../Themes";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Header from "../components/Header";
|
import Header from "../../components/Header";
|
||||||
import BaseBackground from "../components/BaseBackground";
|
import BaseBackground from "../../components/BaseBackground";
|
||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
|
|
||||||
// TODO: better shadows for everything
|
// TODO: better shadows for everything
|
||||||
@@ -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 (
|
|
||||||
<View className='flex-1 items-center justify-center'>
|
|
||||||
<Text>{props.text} : {props.aNumber}</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Counter = () => {
|
|
||||||
const [count, setCount] = useState<number>(0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Button
|
|
||||||
onPress={() => setCount(count + 1)}
|
|
||||||
title={`You tabbed me ${count} times`}/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ManyHelloes = () => {
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<HelloWorld text="first number" aNumber={1}/>
|
|
||||||
<HelloWorld text="second number" aNumber={2}/>
|
|
||||||
<HelloWorld text="third number" aNumber={3}/>
|
|
||||||
<Counter/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ManyHelloes;
|
|
||||||
@@ -2,5 +2,13 @@ import { Stack } from "expo-router";
|
|||||||
import "../../global.css";
|
import "../../global.css";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
return <Stack screenOptions={{ headerShown: false }} />;
|
return (
|
||||||
|
<Stack screenOptions={{ headerShown: false }}>
|
||||||
|
<Stack.Screen name="(tabs)" />
|
||||||
|
<Stack.Screen name="login" />
|
||||||
|
<Stack.Screen name="register" />
|
||||||
|
<Stack.Screen name="event/[id]" />
|
||||||
|
<Stack.Screen name="note/[id]" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
44
apps/client/src/app/event/[id].tsx
Normal file
44
apps/client/src/app/event/[id].tsx
Normal file
@@ -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 (
|
||||||
|
<BaseBackground>
|
||||||
|
<View className="flex-1 p-4">
|
||||||
|
<Text className="text-2xl mb-4">Event Detail</Text>
|
||||||
|
<Text className="text-gray-500 mb-4">ID: {id}</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Title"
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Description"
|
||||||
|
multiline
|
||||||
|
className="w-full border rounded p-2 mb-4 h-24"
|
||||||
|
/>
|
||||||
|
<View className="flex-row gap-2">
|
||||||
|
<Pressable className="bg-blue-500 p-3 rounded flex-1">
|
||||||
|
<Text className="text-white text-center">Save</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable className="bg-red-500 p-3 rounded flex-1">
|
||||||
|
<Text className="text-white text-center">Delete</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</BaseBackground>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventDetailScreen;
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
import React from "react";
|
import { Redirect } from "expo-router";
|
||||||
import Chat from "./Chat";
|
|
||||||
import Calender from "./Calender";
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
return (
|
return <Redirect href="/(tabs)/chat" />;
|
||||||
// <Chat />
|
|
||||||
<Calender />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
apps/client/src/app/login.tsx
Normal file
34
apps/client/src/app/login.tsx
Normal file
@@ -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 (
|
||||||
|
<BaseBackground>
|
||||||
|
<View className="flex-1 justify-center items-center p-4">
|
||||||
|
<Text className="text-2xl mb-8">Login</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Email"
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Password"
|
||||||
|
secureTextEntry
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<Pressable className="bg-blue-500 p-3 rounded w-full">
|
||||||
|
<Text className="text-white text-center">Login</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</BaseBackground>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginScreen;
|
||||||
34
apps/client/src/app/note/[id].tsx
Normal file
34
apps/client/src/app/note/[id].tsx
Normal file
@@ -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 (
|
||||||
|
<BaseBackground>
|
||||||
|
<View className="flex-1 p-4">
|
||||||
|
<Text className="text-2xl mb-4">Note</Text>
|
||||||
|
<Text className="text-gray-500 mb-4">Event ID: {id}</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Write your note here..."
|
||||||
|
multiline
|
||||||
|
className="w-full border rounded p-2 flex-1 mb-4"
|
||||||
|
textAlignVertical="top"
|
||||||
|
/>
|
||||||
|
<Pressable className="bg-blue-500 p-3 rounded">
|
||||||
|
<Text className="text-white text-center">Save Note</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</BaseBackground>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NoteScreen;
|
||||||
45
apps/client/src/app/register.tsx
Normal file
45
apps/client/src/app/register.tsx
Normal file
@@ -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 (
|
||||||
|
<BaseBackground>
|
||||||
|
<View className="flex-1 justify-center items-center p-4">
|
||||||
|
<Text className="text-2xl mb-8">Register</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Email"
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Display Name"
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Password"
|
||||||
|
secureTextEntry
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
secureTextEntry
|
||||||
|
className="w-full border rounded p-2 mb-4"
|
||||||
|
/>
|
||||||
|
<Pressable className="bg-blue-500 p-3 rounded w-full">
|
||||||
|
<Text className="text-white text-center">Register</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</BaseBackground>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RegisterScreen;
|
||||||
24
apps/client/src/components/EventCard.tsx
Normal file
24
apps/client/src/components/EventCard.tsx
Normal file
@@ -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 (
|
||||||
|
<Pressable>
|
||||||
|
<View>
|
||||||
|
<Text>EventCard - Not Implemented</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventCard;
|
||||||
36
apps/client/src/components/EventConfirmDialog.tsx
Normal file
36
apps/client/src/components/EventConfirmDialog.tsx
Normal file
@@ -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 (
|
||||||
|
<Modal visible={false} transparent animationType="fade">
|
||||||
|
<View>
|
||||||
|
<Pressable>
|
||||||
|
<Text>EventConfirmDialog - Not Implemented</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventConfirmDialog;
|
||||||
51
apps/client/src/services/ApiClient.ts
Normal file
51
apps/client/src/services/ApiClient.ts
Normal file
@@ -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<string, string>;
|
||||||
|
body?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function request<T>(
|
||||||
|
_method: HttpMethod,
|
||||||
|
_endpoint: string,
|
||||||
|
_options?: RequestOptions,
|
||||||
|
): Promise<T> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ApiClient = {
|
||||||
|
get: async <T>(
|
||||||
|
endpoint: string,
|
||||||
|
options?: Omit<RequestOptions, "body">,
|
||||||
|
): Promise<T> => {
|
||||||
|
return request<T>("GET", endpoint, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
post: async <T>(
|
||||||
|
endpoint: string,
|
||||||
|
body?: unknown,
|
||||||
|
options?: RequestOptions,
|
||||||
|
): Promise<T> => {
|
||||||
|
return request<T>("POST", endpoint, { ...options, body });
|
||||||
|
},
|
||||||
|
|
||||||
|
put: async <T>(
|
||||||
|
endpoint: string,
|
||||||
|
body?: unknown,
|
||||||
|
options?: RequestOptions,
|
||||||
|
): Promise<T> => {
|
||||||
|
return request<T>("PUT", endpoint, { ...options, body });
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: async <T>(
|
||||||
|
endpoint: string,
|
||||||
|
options?: Omit<RequestOptions, "body">,
|
||||||
|
): Promise<T> => {
|
||||||
|
return request<T>("DELETE", endpoint, options);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { API_BASE_URL };
|
||||||
19
apps/client/src/services/AuthService.ts
Normal file
19
apps/client/src/services/AuthService.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { LoginDTO, CreateUserDTO, AuthResponse } from '@caldav/shared';
|
||||||
|
|
||||||
|
export const AuthService = {
|
||||||
|
login: async (_credentials: LoginDTO): Promise<AuthResponse> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
register: async (_data: CreateUserDTO): Promise<AuthResponse> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
logout: async (): Promise<void> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: async (): Promise<AuthResponse> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
};
|
||||||
39
apps/client/src/services/ChatService.ts
Normal file
39
apps/client/src/services/ChatService.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
SendMessageDTO,
|
||||||
|
ChatResponse,
|
||||||
|
ChatMessage,
|
||||||
|
ConversationSummary,
|
||||||
|
GetMessagesOptions,
|
||||||
|
CalendarEvent,
|
||||||
|
} from '@caldav/shared';
|
||||||
|
|
||||||
|
export const ChatService = {
|
||||||
|
sendMessage: async (_data: SendMessageDTO): Promise<ChatResponse> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmEvent: async (
|
||||||
|
_conversationId: string,
|
||||||
|
_messageId: string
|
||||||
|
): Promise<CalendarEvent> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
rejectEvent: async (
|
||||||
|
_conversationId: string,
|
||||||
|
_messageId: string
|
||||||
|
): Promise<void> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
getConversations: async (): Promise<ConversationSummary[]> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
getConversation: async (
|
||||||
|
_id: string,
|
||||||
|
_options?: GetMessagesOptions
|
||||||
|
): Promise<ChatMessage[]> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
};
|
||||||
27
apps/client/src/services/EventService.ts
Normal file
27
apps/client/src/services/EventService.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { CalendarEvent, CreateEventDTO, UpdateEventDTO } from '@caldav/shared';
|
||||||
|
|
||||||
|
export const EventService = {
|
||||||
|
getAll: async (): Promise<CalendarEvent[]> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
getById: async (_id: string): Promise<CalendarEvent> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
getByDateRange: async (_start: Date, _end: Date): Promise<CalendarEvent[]> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
create: async (_data: CreateEventDTO): Promise<CalendarEvent> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async (_id: string, _data: UpdateEventDTO): Promise<CalendarEvent> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: async (_id: string): Promise<void> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
};
|
||||||
4
apps/client/src/services/index.ts
Normal file
4
apps/client/src/services/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { ApiClient, API_BASE_URL } from './ApiClient';
|
||||||
|
export { AuthService } from './AuthService';
|
||||||
|
export { EventService } from './EventService';
|
||||||
|
export { ChatService } from './ChatService';
|
||||||
26
apps/client/src/stores/AuthStore.ts
Normal file
26
apps/client/src/stores/AuthStore.ts
Normal file
@@ -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<AuthState>((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');
|
||||||
|
},
|
||||||
|
}));
|
||||||
26
apps/client/src/stores/EventsStore.ts
Normal file
26
apps/client/src/stores/EventsStore.ts
Normal file
@@ -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<CalendarEvent>) => void;
|
||||||
|
deleteEvent: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useEventsStore = create<EventsState>((set) => ({
|
||||||
|
events: [],
|
||||||
|
setEvents: (_events: CalendarEvent[]) => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
addEvent: (_event: CalendarEvent) => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
updateEvent: (_id: string, _event: Partial<CalendarEvent>) => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
deleteEvent: (_id: string) => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
}));
|
||||||
2
apps/client/src/stores/index.ts
Normal file
2
apps/client/src/stores/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { useAuthStore } from './AuthStore';
|
||||||
|
export { useEventsStore } from './EventsStore';
|
||||||
@@ -2,12 +2,14 @@
|
|||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "CommonJS",
|
"module": "ESNext",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"jsx": "react-native",
|
||||||
|
"moduleResolution": "bundler"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -47,7 +47,8 @@
|
|||||||
"react-native-safe-area-context": "5.6.0",
|
"react-native-safe-area-context": "5.6.0",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
"react-native-worklets": "0.5.1"
|
"react-native-worklets": "0.5.1",
|
||||||
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
@@ -15412,6 +15413,35 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"packages/shared": {
|
||||||
"name": "@caldav/shared",
|
"name": "@caldav/shared",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user