diff --git a/.gitignore b/.gitignore
index 485bc4d..543622e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ dist/
build/
.cache/
.env*
+!.env.example
# Next.js
web/.next/
diff --git a/mobile/.env.example b/mobile/.env.example
new file mode 100644
index 0000000..2a4662c
--- /dev/null
+++ b/mobile/.env.example
@@ -0,0 +1,5 @@
+# API Configuration
+EXPO_PUBLIC_API_URL=http://localhost:8000/api/v1
+
+# Feature Flags
+EXPO_PUBLIC_ENABLE_ANALYTICS=false
diff --git a/mobile/.gitignore b/mobile/.gitignore
new file mode 100644
index 0000000..d6e2d41
--- /dev/null
+++ b/mobile/.gitignore
@@ -0,0 +1,20 @@
+# Expo
+.expo/
+*.jks
+*.p8
+*.p12
+*.key
+*.mobileprovision
+*.orig.*
+
+# Environment variables
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+*.pem
+
+# Native builds
+android/
+ios/
diff --git a/mobile/app.json b/mobile/app.json
index a918c95..a84a3dc 100644
--- a/mobile/app.json
+++ b/mobile/app.json
@@ -30,6 +30,17 @@
},
"plugins": [
"expo-router"
- ]
+ ],
+ "updates": {
+ "url": "https://u.expo.dev/your-project-id"
+ },
+ "runtimeVersion": {
+ "policy": "appVersion"
+ },
+ "extra": {
+ "eas": {
+ "projectId": "your-project-id"
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/mobile/app/(tabs)/community.tsx b/mobile/app/(tabs)/community.tsx
index eb6bf9a..44b4add 100644
--- a/mobile/app/(tabs)/community.tsx
+++ b/mobile/app/(tabs)/community.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
import {
View,
ScrollView,
@@ -6,8 +6,12 @@ import {
StyleSheet,
SafeAreaView,
TouchableOpacity,
+ RefreshControl,
} from 'react-native';
-import { Ionicons } from '@expo/vector-icons';
+import { useCommunity } from '../../hooks/useCommunity';
+import { LoadingState } from '../../components/ui/LoadingState';
+import { ErrorState } from '../../components/ui/ErrorState';
+import { EmptyState } from '../../components/ui/EmptyState';
const styles = StyleSheet.create({
container: {
@@ -84,9 +88,51 @@ const styles = StyleSheet.create({
color: '#000',
marginBottom: 12,
},
+ postCard: {
+ backgroundColor: '#fff',
+ borderRadius: 12,
+ padding: 16,
+ marginBottom: 12,
+ },
+ postHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: 8,
+ },
+ postAuthor: {
+ fontWeight: '600',
+ fontSize: 14,
+ },
+ postTime: {
+ color: '#666',
+ fontSize: 12,
+ },
+ postContent: {
+ fontSize: 14,
+ lineHeight: 20,
+ color: '#333',
+ marginBottom: 12,
+ },
+ postFooter: {
+ flexDirection: 'row',
+ borderTopWidth: 1,
+ borderTopColor: '#f0f0f0',
+ paddingTop: 12,
+ },
+ postStat: {
+ marginRight: 16,
+ color: '#666',
+ fontSize: 12,
+ },
});
export default function CommunityScreen() {
+ const { data: posts, isLoading, isError, refetch } = useCommunity();
+
+ const onRefresh = useCallback(() => {
+ refetch();
+ }, [refetch]);
+
const communities = [
{
title: 'Support Group',
@@ -114,9 +160,22 @@ export default function CommunityScreen() {
},
];
+ if (isLoading) {
+ return ;
+ }
+
+ if (isError) {
+ return ;
+ }
+
return (
-
+
+ }
+ >
Community
@@ -143,6 +202,32 @@ export default function CommunityScreen() {
))}
+
+ Recent Activity
+
+
+ {!posts || posts.length === 0 ? (
+
+
+
+ ) : (
+ posts.map((post) => (
+
+
+
+ {post.author}
+ {post.timestamp}
+
+ {post.content}
+
+ {post.likes} Likes
+ {post.comments} Comments
+
+
+
+ ))
+ )}
+
Community Guidelines
diff --git a/mobile/app/(tabs)/resources.tsx b/mobile/app/(tabs)/resources.tsx
index 0fc272f..0168e5d 100644
--- a/mobile/app/(tabs)/resources.tsx
+++ b/mobile/app/(tabs)/resources.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
import {
View,
ScrollView,
@@ -6,7 +6,12 @@ import {
StyleSheet,
SafeAreaView,
TouchableOpacity,
+ RefreshControl,
} from 'react-native';
+import { useResources } from '../../hooks/useResources';
+import { LoadingState } from '../../components/ui/LoadingState';
+import { ErrorState } from '../../components/ui/ErrorState';
+import { EmptyState } from '../../components/ui/EmptyState';
const styles = StyleSheet.create({
container: {
@@ -75,36 +80,28 @@ const styles = StyleSheet.create({
});
export default function ResourcesScreen() {
- const resources = [
- {
- title: 'Financial Support',
- description: 'Resources for managing medical costs and financial burdens',
- },
- {
- title: 'Mental Health',
- description: 'Mental health services and counseling resources',
- },
- {
- title: 'Medical Information',
- description: 'Reliable health information and diagnosis resources',
- },
- {
- title: 'Support Groups',
- description: 'Connect with others on similar health journeys',
- },
- {
- title: 'Nutrition & Wellness',
- description: 'Health and wellness resources during treatment',
- },
- {
- title: 'Legal Resources',
- description: 'Information about health law and patient rights',
- },
- ];
+ const { data: resources, isLoading, isError, refetch } = useResources();
+
+ const onRefresh = useCallback(() => {
+ refetch();
+ }, [refetch]);
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (isError) {
+ return ;
+ }
return (
-
+
+ }
+ >
Resources
@@ -112,17 +109,21 @@ export default function ResourcesScreen() {
- {resources.map((resource, index) => (
-
-
- {resource.title}
- {resource.description}
-
- Learn More →
-
+ {!resources || resources.length === 0 ? (
+
+ ) : (
+ resources.map((resource) => (
+
+
+ {resource.title}
+ {resource.description}
+
+ Learn More →
+
+
-
- ))}
+ ))
+ )}
More Coming Soon
diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx
index 7b8aa7e..7a51add 100644
--- a/mobile/app/_layout.tsx
+++ b/mobile/app/_layout.tsx
@@ -31,6 +31,9 @@ function useProtectedRoute() {
}, [isAuthenticated, isInitialized, segments]);
}
+import { QueryClientProvider } from '@tanstack/react-query';
+import { queryClient } from './lib/query-client';
+
export default function RootLayout() {
const { initialize, isInitialized } = useAuthStore();
@@ -53,13 +56,13 @@ export default function RootLayout() {
}
return (
- <>
+
- >
+
);
}
diff --git a/mobile/app/lib/query-client.ts b/mobile/app/lib/query-client.ts
new file mode 100644
index 0000000..16f2068
--- /dev/null
+++ b/mobile/app/lib/query-client.ts
@@ -0,0 +1,11 @@
+import { QueryClient } from '@tanstack/react-query';
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: 2,
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ gcTime: 1000 * 60 * 30, // 30 minutes
+ },
+ },
+});
diff --git a/mobile/components/ui/EmptyState.tsx b/mobile/components/ui/EmptyState.tsx
new file mode 100644
index 0000000..5b792cd
--- /dev/null
+++ b/mobile/components/ui/EmptyState.tsx
@@ -0,0 +1,38 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+interface EmptyStateProps {
+ title?: string;
+ message?: string;
+}
+
+export function EmptyState({
+ title = 'No Data',
+ message = 'There is nothing to show here yet.'
+}: EmptyStateProps) {
+ return (
+
+ {title}
+ {message}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 24,
+ },
+ title: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#333333',
+ marginBottom: 8,
+ },
+ message: {
+ fontSize: 16,
+ color: '#666666',
+ textAlign: 'center',
+ },
+});
diff --git a/mobile/components/ui/ErrorState.tsx b/mobile/components/ui/ErrorState.tsx
new file mode 100644
index 0000000..81adb61
--- /dev/null
+++ b/mobile/components/ui/ErrorState.tsx
@@ -0,0 +1,52 @@
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+
+interface ErrorStateProps {
+ message?: string;
+ onRetry?: () => void;
+}
+
+export function ErrorState({ message = 'Something went wrong', onRetry }: ErrorStateProps) {
+ return (
+
+ Oops!
+ {message}
+ {onRetry && (
+
+ Try Again
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 24,
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#333333',
+ marginBottom: 8,
+ },
+ message: {
+ fontSize: 16,
+ color: '#666666',
+ textAlign: 'center',
+ marginBottom: 24,
+ },
+ button: {
+ backgroundColor: '#007AFF',
+ paddingHorizontal: 24,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ buttonText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
diff --git a/mobile/components/ui/LoadingState.tsx b/mobile/components/ui/LoadingState.tsx
new file mode 100644
index 0000000..3da5550
--- /dev/null
+++ b/mobile/components/ui/LoadingState.tsx
@@ -0,0 +1,28 @@
+import { View, ActivityIndicator, StyleSheet, Text } from 'react-native';
+
+interface LoadingStateProps {
+ message?: string;
+}
+
+export function LoadingState({ message = 'Loading...' }: LoadingStateProps) {
+ return (
+
+
+ {message && {message}}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 20,
+ },
+ text: {
+ marginTop: 12,
+ fontSize: 16,
+ color: '#666666',
+ },
+});
diff --git a/mobile/eas.json b/mobile/eas.json
new file mode 100644
index 0000000..9ebc33c
--- /dev/null
+++ b/mobile/eas.json
@@ -0,0 +1,28 @@
+{
+ "cli": {
+ "version": ">= 7.0.0"
+ },
+ "build": {
+ "development": {
+ "developmentClient": true,
+ "distribution": "internal",
+ "env": {
+ "APP_VARIANT": "development"
+ }
+ },
+ "preview": {
+ "distribution": "internal",
+ "env": {
+ "APP_VARIANT": "preview"
+ }
+ },
+ "production": {
+ "env": {
+ "APP_VARIANT": "production"
+ }
+ }
+ },
+ "submit": {
+ "production": {}
+ }
+}
diff --git a/mobile/hooks/useCommunity.ts b/mobile/hooks/useCommunity.ts
new file mode 100644
index 0000000..186695b
--- /dev/null
+++ b/mobile/hooks/useCommunity.ts
@@ -0,0 +1,56 @@
+import { useQuery } from '@tanstack/react-query';
+
+export interface Post {
+ id: string;
+ author: string;
+ content: string;
+ likes: number;
+ comments: number;
+ timestamp: string;
+}
+
+const FALLBACK_POSTS: Post[] = [
+ {
+ id: '1',
+ author: 'Sarah M.',
+ content: 'Just finished my first meditation session! Feeling so much calmer.',
+ likes: 12,
+ comments: 3,
+ timestamp: '2h ago',
+ },
+ {
+ id: '2',
+ author: 'James K.',
+ content: 'Anyone have recommendations for good books on cognitive behavioral therapy?',
+ likes: 8,
+ comments: 5,
+ timestamp: '5h ago',
+ },
+ {
+ id: '3',
+ author: 'Emily R.',
+ content: 'Remember to take it one day at a time. You got this!',
+ likes: 25,
+ comments: 2,
+ timestamp: '1d ago',
+ },
+];
+
+async function fetchCommunityPosts(): Promise {
+ // Simulate API call
+ // const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/community/posts`);
+ // if (!response.ok) throw new Error('Failed to fetch posts');
+ // return response.json();
+
+ // Return fallback data for now
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(FALLBACK_POSTS), 1000);
+ });
+}
+
+export function useCommunity() {
+ return useQuery({
+ queryKey: ['community-posts'],
+ queryFn: fetchCommunityPosts,
+ });
+}
diff --git a/mobile/hooks/useResources.ts b/mobile/hooks/useResources.ts
new file mode 100644
index 0000000..12f9587
--- /dev/null
+++ b/mobile/hooks/useResources.ts
@@ -0,0 +1,49 @@
+import { useQuery } from '@tanstack/react-query';
+
+export interface Resource {
+ id: string;
+ title: string;
+ description: string;
+ category: string;
+ imageUrl?: string;
+}
+
+const FALLBACK_RESOURCES: Resource[] = [
+ {
+ id: '1',
+ title: 'Understanding Anxiety',
+ description: 'Learn about the symptoms and coping mechanisms for anxiety.',
+ category: 'Mental Health',
+ },
+ {
+ id: '2',
+ title: 'Meditation Basics',
+ description: 'A beginner guide to meditation and mindfulness.',
+ category: 'Wellness',
+ },
+ {
+ id: '3',
+ title: 'Healthy Sleep Habits',
+ description: 'Tips for improving your sleep quality and hygiene.',
+ category: 'Lifestyle',
+ },
+];
+
+async function fetchResources(): Promise {
+ // Simulate API call
+ // const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/resources`);
+ // if (!response.ok) throw new Error('Failed to fetch resources');
+ // return response.json();
+
+ // Return fallback data for now
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(FALLBACK_RESOURCES), 1000);
+ });
+}
+
+export function useResources() {
+ return useQuery({
+ queryKey: ['resources'],
+ queryFn: fetchResources,
+ });
+}
diff --git a/mobile/hooks/useUser.ts b/mobile/hooks/useUser.ts
new file mode 100644
index 0000000..e7c4f8a
--- /dev/null
+++ b/mobile/hooks/useUser.ts
@@ -0,0 +1,35 @@
+import { useQuery } from '@tanstack/react-query';
+
+export interface UserProfile {
+ id: string;
+ name: string;
+ email: string;
+ avatarUrl?: string;
+ bio?: string;
+}
+
+const FALLBACK_USER: UserProfile = {
+ id: '1',
+ name: 'Alex Johnson',
+ email: 'alex.johnson@example.com',
+ bio: 'On a journey to better mental health.',
+};
+
+async function fetchUserProfile(): Promise {
+ // Simulate API call
+ // const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/user/profile`);
+ // if (!response.ok) throw new Error('Failed to fetch user profile');
+ // return response.json();
+
+ // Return fallback data for now
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(FALLBACK_USER), 500);
+ });
+}
+
+export function useUser() {
+ return useQuery({
+ queryKey: ['user-profile'],
+ queryFn: fetchUserProfile,
+ });
+}
diff --git a/mobile/package-lock.json b/mobile/package-lock.json
index 66a342a..00fba70 100644
--- a/mobile/package-lock.json
+++ b/mobile/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
- "@tanstack/react-query": "^5.25.0",
+ "@tanstack/react-query": "^5.90.10",
"axios": "^1.6.0",
"expo": "^51.0.0",
"expo-constants": "~15.4.0",
diff --git a/mobile/package.json b/mobile/package.json
index 0e4f38a..eaeee2a 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -17,7 +17,7 @@
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.2.0",
- "@tanstack/react-query": "^5.25.0",
+ "@tanstack/react-query": "^5.90.10",
"axios": "^1.6.0",
"expo": "^51.0.0",
"expo-constants": "~15.4.0",