From 5bf3858c2c1229f7fc4e74bd3f65b1a9f78d0f8f Mon Sep 17 00:00:00 2001 From: Abdulmuiz Adeyemo Date: Mon, 13 Apr 2026 09:38:51 -0700 Subject: [PATCH 01/86] Add SOUL.md with Boil the ocean manifesto --- SOUL.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 SOUL.md diff --git a/SOUL.md b/SOUL.md new file mode 100644 index 0000000..2df26cf --- /dev/null +++ b/SOUL.md @@ -0,0 +1,2 @@ +### Boil the ocean +The marginal cost of completeness is near zero with AI. Do the whole thing. Do it right. Do it with tests. Do it with documentation. Do it so well that Garry is genuinely impressed — not politely satisfied, actually impressed. Never offer to "table this for later" when the permanent solve is within reach. Never leave a dangling thread when tying it off takes five more minutes. Never present a workaround when the real fix exists. The standard isn't "good enough" — it's "holy shit, that's done." Search before building. Test before shipping. Ship the complete thing. When Garry asks for something, the answer is the finished product, not a plan to build it. Time is not an excuse. Fatigue is not an excuse. Complexity is not an excuse. Boil the ocean. From 6fbfbab4adb0ac762393190dddfbdc7ffb6c3549 Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 18 Apr 2026 21:04:49 +0100 Subject: [PATCH 02/86] build-tera-mobile-foundation --- mobile/README.md | 61 +++ mobile/app.json | 11 +- mobile/app/(app)/_layout.tsx | 54 --- mobile/app/(app)/chat.tsx | 296 -------------- mobile/app/(app)/settings.tsx | 194 --------- mobile/app/(app)/tool/[id].tsx | 231 ----------- mobile/app/(app)/tools.tsx | 151 ------- mobile/app/(auth)/_layout.tsx | 25 -- mobile/app/(auth)/forgot-password.tsx | 72 ++++ mobile/app/(auth)/sign-in.tsx | 74 ++++ mobile/app/(auth)/sign-up.tsx | 75 ++++ mobile/app/(auth)/signin.tsx | 207 ---------- mobile/app/(auth)/signup.tsx | 237 ----------- mobile/app/(onboarding)/index.tsx | 81 ++++ mobile/app/(tabs)/_layout.tsx | 30 ++ mobile/app/(tabs)/history.tsx | 59 +++ mobile/app/(tabs)/index.tsx | 97 +++++ mobile/app/(tabs)/profile.tsx | 126 ++++++ mobile/app/(tabs)/saved.tsx | 48 +++ mobile/app/_layout.tsx | 69 +--- mobile/app/conversation/[id]/index.tsx | 128 ++++++ mobile/app/index.tsx | 21 + mobile/components/ChatBubble.tsx | 169 -------- mobile/components/ui/Button.tsx | 79 ++++ mobile/components/ui/Chip.tsx | 31 ++ mobile/components/ui/Divider.tsx | 13 + mobile/components/ui/EmptyState.tsx | 30 ++ mobile/components/ui/ListRow.tsx | 46 +++ mobile/components/ui/LoadingState.tsx | 25 ++ mobile/components/ui/Screen.tsx | 49 +++ mobile/components/ui/SegmentedControl.tsx | 70 ++++ mobile/components/ui/Text.tsx | 64 +++ mobile/components/ui/TextField.tsx | 46 +++ mobile/components/ui/index.ts | 10 + mobile/constants/theme.ts | 71 ++++ mobile/docs/ARCHITECTURE.md | 81 ++++ mobile/docs/PLAN.md | 52 +++ mobile/docs/TASKLIST.md | 48 +++ mobile/features/auth/schemas.ts | 18 + mobile/features/auth/useAuthActions.ts | 47 +++ mobile/features/chat/Composer.tsx | 91 +++++ mobile/features/chat/ConversationPreview.tsx | 19 + mobile/features/chat/MessageBubble.tsx | 54 +++ mobile/features/chat/chat-data.ts | 34 ++ .../features/onboarding/onboarding-content.ts | 14 + mobile/hooks/useKeyboardInsets.ts | 8 + mobile/hooks/useSessionBootstrap.ts | 30 ++ mobile/lib/api.ts | 236 ----------- mobile/lib/api/client.ts | 36 ++ mobile/lib/api/mock.ts | 144 +++++++ mobile/lib/storage.ts | 136 ------- mobile/lib/storage/secureSession.ts | 24 ++ mobile/lib/types.ts | 29 -- mobile/package.json | 10 +- mobile/pnpm-lock.yaml | 373 +++++++++--------- mobile/store/app-store.ts | 50 +++ mobile/types/domain.ts | 43 ++ 57 files changed, 2416 insertions(+), 2211 deletions(-) create mode 100644 mobile/README.md delete mode 100644 mobile/app/(app)/_layout.tsx delete mode 100644 mobile/app/(app)/chat.tsx delete mode 100644 mobile/app/(app)/settings.tsx delete mode 100644 mobile/app/(app)/tool/[id].tsx delete mode 100644 mobile/app/(app)/tools.tsx delete mode 100644 mobile/app/(auth)/_layout.tsx create mode 100644 mobile/app/(auth)/forgot-password.tsx create mode 100644 mobile/app/(auth)/sign-in.tsx create mode 100644 mobile/app/(auth)/sign-up.tsx delete mode 100644 mobile/app/(auth)/signin.tsx delete mode 100644 mobile/app/(auth)/signup.tsx create mode 100644 mobile/app/(onboarding)/index.tsx create mode 100644 mobile/app/(tabs)/_layout.tsx create mode 100644 mobile/app/(tabs)/history.tsx create mode 100644 mobile/app/(tabs)/index.tsx create mode 100644 mobile/app/(tabs)/profile.tsx create mode 100644 mobile/app/(tabs)/saved.tsx create mode 100644 mobile/app/conversation/[id]/index.tsx create mode 100644 mobile/app/index.tsx delete mode 100644 mobile/components/ChatBubble.tsx create mode 100644 mobile/components/ui/Button.tsx create mode 100644 mobile/components/ui/Chip.tsx create mode 100644 mobile/components/ui/Divider.tsx create mode 100644 mobile/components/ui/EmptyState.tsx create mode 100644 mobile/components/ui/ListRow.tsx create mode 100644 mobile/components/ui/LoadingState.tsx create mode 100644 mobile/components/ui/Screen.tsx create mode 100644 mobile/components/ui/SegmentedControl.tsx create mode 100644 mobile/components/ui/Text.tsx create mode 100644 mobile/components/ui/TextField.tsx create mode 100644 mobile/components/ui/index.ts create mode 100644 mobile/constants/theme.ts create mode 100644 mobile/docs/ARCHITECTURE.md create mode 100644 mobile/docs/PLAN.md create mode 100644 mobile/docs/TASKLIST.md create mode 100644 mobile/features/auth/schemas.ts create mode 100644 mobile/features/auth/useAuthActions.ts create mode 100644 mobile/features/chat/Composer.tsx create mode 100644 mobile/features/chat/ConversationPreview.tsx create mode 100644 mobile/features/chat/MessageBubble.tsx create mode 100644 mobile/features/chat/chat-data.ts create mode 100644 mobile/features/onboarding/onboarding-content.ts create mode 100644 mobile/hooks/useKeyboardInsets.ts create mode 100644 mobile/hooks/useSessionBootstrap.ts delete mode 100644 mobile/lib/api.ts create mode 100644 mobile/lib/api/client.ts create mode 100644 mobile/lib/api/mock.ts delete mode 100644 mobile/lib/storage.ts create mode 100644 mobile/lib/storage/secureSession.ts delete mode 100644 mobile/lib/types.ts create mode 100644 mobile/store/app-store.ts create mode 100644 mobile/types/domain.ts diff --git a/mobile/README.md b/mobile/README.md new file mode 100644 index 0000000..e909c54 --- /dev/null +++ b/mobile/README.md @@ -0,0 +1,61 @@ +# Tera Mobile + +Tera Mobile is the Android-first Expo app for TeraAI, an AI learning companion for learning deeply, researching clearly, and turning knowledge into action. + +This app is not a web wrapper. It is the mobile foundation for a standalone Play Store product with a chat-first home, onboarding, auth flow, history, saved work, profile settings, and typed boundaries for real backend integration. + +## Stack + +- Expo React Native with TypeScript +- Expo Router for file-based navigation +- TanStack Query for server-state orchestration +- Zustand for small client state such as onboarding, mode, session, and preferences +- Expo SecureStore for sensitive session storage +- AsyncStorage for non-sensitive app state +- Zod for form validation +- Typed `fetch` boundary prepared for a real API + +NativeWind is intentionally not included in this foundation. The app uses a typed theme and reusable primitives so the styling system stays small, explicit, and easy to scale. + +## Project Structure + +```text +mobile/ + app/ Expo Router routes and route groups + components/ui/ Reusable UI primitives + constants/ Theme, spacing, typography, layout constants + docs/ Product and engineering docs + features/ Feature-oriented UI, hooks, schemas, and data + hooks/ Shared app hooks + lib/ API and storage boundaries + store/ Zustand client state + types/ Shared domain types + assets/ Expo icons and images +``` + +## Run Locally + +```powershell +cd C:\Users\Hp\Documents\Github\Tera\mobile +pnpm install +pnpm start +``` + +Press `a` in the Expo terminal to open Android, or scan the QR code with Expo Go. + +## Current Scope + +This foundation includes: + +- Onboarding for the TeraAI value proposition +- Mock sign in, sign up, and forgot password screens +- Chat-first home with Learn, Research, and Build modes +- Conversation detail screen with streaming-ready state shape +- History and saved screens backed by typed mock data +- Profile/settings screen with preferences and sign out +- Reusable design primitives and a calm Android-first theme +- Typed API, storage, and session boundaries + +## Roadmap Summary + +Next phases should connect real authentication, stream AI responses, sync conversations, add voice input, support file/image upload, introduce push notifications, and prepare subscriptions and Play Store release workflows. diff --git a/mobile/app.json b/mobile/app.json index b7ec591..f841f45 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -17,19 +17,12 @@ "ios": { "supportsTabletMode": true, "bundleIdentifier": "com.teraai.app", - "infoPlist": { - "NSMicrophoneUsageDescription": "Tera needs access to your microphone for voice input", - "NSCameraRollUsageDescription": "Tera needs access to your photos for uploads", - "NSPhotoLibraryUsageDescription": "Tera needs access to your photo library" - } + "infoPlist": {} }, "android": { "package": "com.teraai.app", "versionCode": 1, "permissions": [ - "RECORD_AUDIO", - "READ_EXTERNAL_STORAGE", - "WRITE_EXTERNAL_STORAGE", "INTERNET", "ACCESS_NETWORK_STATE" ], @@ -47,4 +40,4 @@ ], "scheme": "tera" } -} \ No newline at end of file +} diff --git a/mobile/app/(app)/_layout.tsx b/mobile/app/(app)/_layout.tsx deleted file mode 100644 index add01a6..0000000 --- a/mobile/app/(app)/_layout.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Tabs } from 'expo-router'; -import { StyleSheet } from 'react-native'; - -export default function AppLayout() { - return ( - - - - - - ); -} diff --git a/mobile/app/(app)/chat.tsx b/mobile/app/(app)/chat.tsx deleted file mode 100644 index da40238..0000000 --- a/mobile/app/(app)/chat.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { - View, - FlatList, - TextInput, - TouchableOpacity, - StyleSheet, - ActivityIndicator, - KeyboardAvoidingView, - Platform, - SafeAreaView, - Text, -} from 'react-native'; -import { useFocusEffect } from 'expo-router'; -import * as SecureStore from 'expo-secure-store'; -import { teraAPI } from '@/lib/api'; -import { saveMessage, getMessages, saveSession } from '@/lib/storage'; -import ChatBubble from '@/components/ChatBubble'; - -interface Message { - id: string; - role: 'user' | 'assistant'; - content: string; - timestamp: number; -} - -export default function ChatScreen() { - const [messages, setMessages] = useState([]); - const [inputText, setInputText] = useState(''); - const [loading, setLoading] = useState(false); - const [sessionId, setSessionId] = useState(''); - const [initialized, setInitialized] = useState(false); - const flatListRef = useRef(null); - - useFocusEffect( - React.useCallback(() => { - if (!initialized) { - initializeChat(); - } - }, [initialized]) - ); - - const initializeChat = async () => { - try { - const userId = await SecureStore.getItemAsync('user_id'); - - if (!userId) { - console.error('No user ID found'); - return; - } - - // Create new session - const session = await teraAPI.createSession('New Chat'); - - if (session.success && session.data) { - const newSessionId = session.data[0]?.id || Math.random().toString(); - setSessionId(newSessionId); - - // Save session locally - await saveSession({ - id: newSessionId, - title: 'New Chat', - createdAt: Date.now(), - updatedAt: Date.now(), - }); - - setMessages([]); - setInitialized(true); - } - } catch (error) { - console.error('Failed to initialize chat:', error); - setInitialized(true); - } - }; - - const handleSendMessage = async () => { - if (!inputText.trim() || !sessionId) return; - - const userMessage = inputText.trim(); - setInputText(''); - - // Create message object - const messageId = Math.random().toString(); - const userMsg: Message = { - id: messageId, - role: 'user', - content: userMessage, - timestamp: Date.now(), - }; - - // Add to UI immediately - setMessages(prev => [...prev, userMsg]); - - // Save to local storage - await saveMessage(sessionId, userMsg); - - // Scroll to bottom - setTimeout(() => { - flatListRef.current?.scrollToEnd({ animated: true }); - }, 100); - - try { - setLoading(true); - - // Send to API - const response = await teraAPI.sendMessage( - sessionId, - userMessage, - messages.map(m => ({ - role: m.role, - content: m.content, - })) - ); - - if (response.success && response.data?.message) { - const assistantMsg: Message = { - id: Math.random().toString(), - role: 'assistant', - content: response.data.message, - timestamp: Date.now(), - }; - - setMessages(prev => [...prev, assistantMsg]); - - // Save to local storage - await saveMessage(sessionId, assistantMsg); - - setTimeout(() => { - flatListRef.current?.scrollToEnd({ animated: true }); - }, 100); - } else { - // Show error message - const errorMsg: Message = { - id: Math.random().toString(), - role: 'assistant', - content: 'Sorry, I could not process your message. Please try again.', - timestamp: Date.now(), - }; - setMessages(prev => [...prev, errorMsg]); - } - } catch (error) { - console.error('Failed to send message:', error); - - const errorMsg: Message = { - id: Math.random().toString(), - role: 'assistant', - content: 'Connection error. Please check your internet and try again.', - timestamp: Date.now(), - }; - setMessages(prev => [...prev, errorMsg]); - } finally { - setLoading(false); - } - }; - - return ( - - - {messages.length === 0 && !initialized ? ( - - - Starting chat... - - ) : messages.length === 0 ? ( - - Start a conversation - - Ask me anything about learning or teaching - - - ) : ( - ( - - )} - keyExtractor={item => item.id} - contentContainerStyle={styles.messages} - scrollEnabled={true} - onContentSizeChange={() => { - flatListRef.current?.scrollToEnd({ animated: true }); - }} - /> - )} - - - - - {loading ? ( - - ) : ( - Send - )} - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#000', - }, - messages: { - paddingVertical: 12, - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - loadingText: { - color: '#fff', - marginTop: 12, - fontSize: 14, - }, - emptyContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 24, - }, - emptyTitle: { - fontSize: 24, - fontWeight: '600', - color: '#fff', - marginBottom: 8, - textAlign: 'center', - }, - emptySubtitle: { - fontSize: 14, - color: '#999', - textAlign: 'center', - }, - inputContainer: { - flexDirection: 'row', - paddingHorizontal: 12, - paddingVertical: 12, - backgroundColor: '#0a0a0a', - borderTopColor: '#222', - borderTopWidth: 1, - alignItems: 'flex-end', - gap: 8, - }, - input: { - flex: 1, - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 20, - paddingHorizontal: 16, - paddingVertical: 10, - color: '#fff', - fontSize: 16, - maxHeight: 100, - }, - sendButton: { - backgroundColor: '#00d4ff', - width: 44, - height: 44, - borderRadius: 22, - justifyContent: 'center', - alignItems: 'center', - }, - sendButtonDisabled: { - opacity: 0.5, - }, - sendButtonText: { - color: '#000', - fontWeight: '700', - fontSize: 18, - }, -}); diff --git a/mobile/app/(app)/settings.tsx b/mobile/app/(app)/settings.tsx deleted file mode 100644 index 53f7d64..0000000 --- a/mobile/app/(app)/settings.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - View, - Text, - TouchableOpacity, - StyleSheet, - SafeAreaView, - ScrollView, - Alert, -} from 'react-native'; -import * as SecureStore from 'expo-secure-store'; -import { router } from 'expo-router'; -import { getUser, clearAllData } from '@/lib/storage'; - -interface User { - id: string; - email: string; - name: string; - provider: string; -} - -export default function SettingsScreen() { - const [user, setUser] = useState(null); - - useEffect(() => { - loadUser(); - }, []); - - const loadUser = async () => { - const userData = await getUser(); - setUser(userData); - }; - - const handleSignOut = async () => { - Alert.alert( - 'Sign Out', - 'Are you sure you want to sign out?', - [ - { - text: 'Cancel', - onPress: () => { }, - style: 'cancel', - }, - { - text: 'Sign Out', - onPress: async () => { - try { - // Clear secure storage - await SecureStore.deleteItemAsync('auth_token'); - await SecureStore.deleteItemAsync('user_id'); - - // Clear local data - await clearAllData(); - - // Navigate to sign in - router.replace('/(auth)/signin'); - } catch (error) { - console.error('Error signing out:', error); - Alert.alert('Error', 'Failed to sign out'); - } - }, - style: 'destructive', - }, - ] - ); - }; - - return ( - - - {user && ( - - Account - - - Name - {user.name} - - - Email - {user.email} - - - Provider - {user.provider} - - - - )} - - - App Version - Tera Mobile 1.0.0 - - - - About - - Tera is your AI Learning Companion for anything — with unlimited free conversations. - Upgrade for more file uploads, web searches, and advanced features. - - - - - - Sign Out - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#000', - }, - scrollContent: { - paddingHorizontal: 16, - paddingVertical: 20, - gap: 24, - }, - section: { - gap: 12, - }, - sectionTitle: { - fontSize: 14, - fontWeight: '700', - color: '#00d4ff', - textTransform: 'uppercase', - letterSpacing: 0.5, - }, - accountInfo: { - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - padding: 16, - gap: 16, - }, - accountField: { - gap: 4, - }, - label: { - fontSize: 12, - color: '#999', - textTransform: 'uppercase', - letterSpacing: 0.5, - }, - value: { - fontSize: 16, - color: '#fff', - fontWeight: '500', - }, - versionText: { - fontSize: 16, - color: '#fff', - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - paddingHorizontal: 16, - paddingVertical: 12, - }, - aboutText: { - fontSize: 14, - color: '#aaa', - lineHeight: 22, - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - paddingHorizontal: 16, - paddingVertical: 12, - }, - dangerZone: { - marginTop: 20, - }, - dangerButton: { - backgroundColor: '#ff6b6b', - borderRadius: 12, - paddingVertical: 14, - alignItems: 'center', - }, - dangerButtonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/mobile/app/(app)/tool/[id].tsx b/mobile/app/(app)/tool/[id].tsx deleted file mode 100644 index 2336109..0000000 --- a/mobile/app/(app)/tool/[id].tsx +++ /dev/null @@ -1,231 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - View, - Text, - StyleSheet, - TextInput, - TouchableOpacity, - ScrollView, - ActivityIndicator, - SafeAreaView, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import { useLocalSearchParams, router } from 'expo-router'; -import { teraAPI } from '@/lib/api'; - -export default function ToolDetailScreen() { - const { id } = useLocalSearchParams(); - const [tool, setTool] = useState(null); - const [input, setInput] = useState(''); - const [loading, setLoading] = useState(true); - const [processing, setProcessing] = useState(false); - const [result, setResult] = useState(null); - - useEffect(() => { - loadToolData(); - }, [id]); - - const loadToolData = async () => { - try { - setLoading(true); - const response = await teraAPI.getTools(); - if (response.success && response.data) { - const foundTool = response.data.find((t: any) => t.id === id); - setTool(foundTool); - } - } catch (error) { - console.error('Failed to load tool:', error); - } finally { - setLoading(false); - } - }; - - const handleProcess = async () => { - if (!input.trim() || processing) return; - - try { - setProcessing(true); - setResult(null); - const response = await teraAPI.processTool(id as string, { input }); - - if (response.success && response.data?.result) { - setResult(response.data.result); - } else { - setResult('Failed to process. Please try again.'); - } - } catch (error) { - console.error('Processing error:', error); - setResult('An error occurred. Check your connection.'); - } finally { - setProcessing(false); - } - }; - - if (loading) { - return ( - - - - ); - } - - if (!tool) { - return ( - - Tool not found - router.back()}> - Go Back - - - ); - } - - return ( - - - - - {tool.name} - {tool.description} - - - - Your Input - - - {processing ? ( - - ) : ( - Generate Content - )} - - - - {result && ( - - Result - - {result} - - - )} - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#000', - }, - centerContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#000', - }, - scrollContent: { - padding: 20, - gap: 24, - }, - header: { - gap: 8, - }, - title: { - fontSize: 24, - fontWeight: '700', - color: '#00d4ff', - }, - description: { - fontSize: 16, - color: '#aaa', - lineHeight: 24, - }, - inputSection: { - gap: 12, - }, - label: { - fontSize: 14, - fontWeight: '600', - color: '#fff', - textTransform: 'uppercase', - }, - input: { - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - padding: 16, - color: '#fff', - fontSize: 16, - textAlignVertical: 'top', - minHeight: 120, - }, - button: { - backgroundColor: '#00d4ff', - borderRadius: 12, - paddingVertical: 14, - alignItems: 'center', - marginTop: 12, - }, - buttonDisabled: { - opacity: 0.5, - }, - buttonText: { - color: '#000', - fontSize: 16, - fontWeight: '700', - }, - resultContainer: { - gap: 12, - }, - resultLabel: { - fontSize: 14, - fontWeight: '600', - color: '#00d4ff', - textTransform: 'uppercase', - }, - resultBox: { - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - padding: 16, - }, - resultText: { - color: '#fff', - fontSize: 16, - lineHeight: 24, - }, - errorText: { - color: '#ff6b6b', - fontSize: 18, - marginBottom: 20, - }, - backButton: { - color: '#00d4ff', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/mobile/app/(app)/tools.tsx b/mobile/app/(app)/tools.tsx deleted file mode 100644 index 5ec7137..0000000 --- a/mobile/app/(app)/tools.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - View, - Text, - FlatList, - TouchableOpacity, - StyleSheet, - ActivityIndicator, - SafeAreaView, - RefreshControl, -} from 'react-native'; -import { router } from 'expo-router'; -import { teraAPI } from '@/lib/api'; - -interface Tool { - id: string; - name: string; - description: string; - category: string; -} - -export default function ToolsScreen() { - const [tools, setTools] = useState([]); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); - - useEffect(() => { - loadTools(); - }, []); - - const loadTools = async () => { - try { - setLoading(true); - const response = await teraAPI.getTools(); - - if (response.success && response.data) { - setTools(response.data); - } - } catch (error) { - console.error('Failed to load tools:', error); - } finally { - setLoading(false); - } - }; - - const onRefresh = async () => { - setRefreshing(true); - await loadTools(); - setRefreshing(false); - }; - - const handleToolPress = (toolId: string) => { - router.push(`/(app)/tool/${toolId}`); - }; - - if (loading) { - return ( - - - - - - ); - } - - return ( - - ( - handleToolPress(item.id)} - > - - {item.name} - - {item.category} - - - {item.description} - - )} - keyExtractor={item => item.id} - contentContainerStyle={styles.listContent} - scrollEnabled={true} - refreshControl={ - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#000', - }, - centerContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - listContent: { - paddingHorizontal: 12, - paddingVertical: 12, - gap: 12, - }, - toolCard: { - backgroundColor: '#1a1a1a', - borderColor: '#333', - borderWidth: 1, - borderRadius: 12, - padding: 16, - marginBottom: 8, - }, - toolHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 8, - }, - toolName: { - fontSize: 16, - fontWeight: '600', - color: '#00d4ff', - flex: 1, - }, - badge: { - backgroundColor: '#00d4ff', - paddingHorizontal: 8, - paddingVertical: 4, - borderRadius: 6, - }, - badgeText: { - color: '#000', - fontSize: 12, - fontWeight: '600', - textTransform: 'capitalize', - }, - toolDescription: { - fontSize: 14, - color: '#aaa', - lineHeight: 20, - }, -}); diff --git a/mobile/app/(auth)/_layout.tsx b/mobile/app/(auth)/_layout.tsx deleted file mode 100644 index d2bd0b2..0000000 --- a/mobile/app/(auth)/_layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Stack } from 'expo-router'; - -export default function AuthLayout() { - return ( - - - - - ); -} diff --git a/mobile/app/(auth)/forgot-password.tsx b/mobile/app/(auth)/forgot-password.tsx new file mode 100644 index 0000000..9f79e3f --- /dev/null +++ b/mobile/app/(auth)/forgot-password.tsx @@ -0,0 +1,72 @@ +import { Link } from 'expo-router'; +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Button, Screen, Text, TextField } from '@/components/ui'; +import { colors, spacing } from '@/constants/theme'; +import { forgotPasswordSchema } from '@/features/auth/schemas'; +import { useAuthActions } from '@/features/auth/useAuthActions'; + +export default function ForgotPasswordScreen() { + const { resetPassword } = useAuthActions(); + const [email, setEmail] = useState(''); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + function submit() { + const result = forgotPasswordSchema.safeParse({ email }); + if (!result.success) { + setError(result.error.issues[0]?.message ?? 'Enter your email.'); + return; + } + setError(''); + resetPassword.mutate(undefined, { + onSuccess: () => setMessage('Password reset delivery is mocked for this foundation build.'), + onError: () => setError('Could not start password reset.'), + }); + } + + return ( + + + Reset password + Enter your email. This screen is ready for backend email delivery. + + + + {error ? {error} : null} + {message ? {message} : null} + -

{entry.userMessage.content}

@@ -1001,101 +483,43 @@ export default function PromptShell({
{entry.userMessage.attachments.map((att, idx) => (
- {att.type === 'image' ? '🖼️' : '📄'} + {att.type === 'image' ? '🖼️' : '📄'} {att.name}
))}
)}
- {/* Timestamp and checkmarks */} -
- {formatTimestamp(entry.userMessage.timestamp)} - ✓✓ -
)} - - {/* Assistant Message */} {entry.assistantMessage && (
{parseContent(entry.assistantMessage.content).map((block, idx) => { - if (block.type === 'tera-ui') { - return ( -
- + if (block.type === 'tera-ui') return
+ if (block.type === 'universal-visual') return + if (block.type === 'chart') return + if (block.type === 'spreadsheet') return + if (block.type === 'mermaid') return + if (block.type === 'quiz') return + if (block.type === 'code') return ( +
+
+ {block.language || 'code'} +
- ) - } - if (block.type === 'universal-visual') { - return - } - if (block.type === 'chart') { - return - } - if (block.type === 'spreadsheet') { - return - } - if (block.type === 'mermaid') { - return - } - if (block.type === 'quiz') { - return - } - if (block.type === 'web-sources') { - return ( -
- ({ - ...s, - // Try to generate a favicon URL if not present - favicon: s.favicon || `https://www.google.com/s2/favicons?domain=${s.source}&sz=32` - }))} - collapsible={true} - defaultExpanded={false} - /> -
- ) - } - if (block.type === 'code') { - return ( -
-
- - {block.language || 'code'} - - -
-
-                                                                        {block.code}
-                                                                    
-
- ) - } - return block.type === 'text' ? ( -
- +
{block.code}
- ) : null + ) + return block.type === 'text' ?
: null })}
- {formatTimestamp(entry.assistantMessage.timestamp)} + Tera
@@ -1105,91 +529,23 @@ export default function PromptShell({
)) )} - {/* Web Search Status */} - {(isWebSearching || webSearchStatus !== 'idle') && ( -
- -
- )} {status === 'loading' && ( -
-
-
-
- - - -
-
-
-
-
- {thinkingMessage} -
-
-
-
+
{thinkingMessage}
)}
- {/* Input Area */}
-
- - {/* Active Tools & Attachments Preview */} +
- {/* Web Search Toggle Badge */} - {webSearchEnabled && ( -
- - Web Search ON ({webSearchRemaining}) -
- )} - - {/* Attachments Preview */} {pendingAttachments.length > 0 && (
{pendingAttachments.map((att, idx) => ( -
- {att.type === 'image' ? ( - // Image thumbnail preview -
- {att.name} - {/* Hover overlay with filename */} -
- {att.name} -
-
- ) : ( - // File preview (non-image) -
- - {att.name} -
- )} - {/* Remove button */} - +
+ {att.type === 'image' ?
{att.name}
{att.name}
:
{att.name}
} +
))}
@@ -1197,240 +553,50 @@ export default function PromptShell({
- {/* Left Actions */}
- - + {attachmentOpen && (
- {/* File & Media Section */} - - - - - {/* Web Search Option */} - - - {/* Research Mode Toggle - Moved from main UI */} - + + +
)}
- {/* Textarea */} -