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:
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 { 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
|
||||
@@ -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
|
||||
@@ -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";
|
||||
|
||||
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 Chat from "./Chat";
|
||||
import Calender from "./Calender";
|
||||
import { Redirect } from "expo-router";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
// <Chat />
|
||||
<Calender />
|
||||
);
|
||||
return <Redirect href="/(tabs)/chat" />;
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user