This is page 2 of 2. Use http://codebase.md/lallen30/mcp-remote-server?page={x} to view the full context. # Directory Structure ``` ├── .gitignore ├── build │ └── index.js ├── package-lock.json ├── package.json ├── README.md ├── resources │ ├── code-examples │ │ └── react-native │ │ ├── assets │ │ │ └── images │ │ │ ├── event.png │ │ │ ├── qr-codeeee13.png │ │ │ └── [email protected] │ │ ├── components │ │ │ ├── Button.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── LoadingSpinner.tsx │ │ │ └── UpdateModal.tsx │ │ ├── config │ │ │ ├── apiConfig.ts │ │ │ └── environment.ts │ │ ├── hooks │ │ │ ├── useAppUpdate.ts │ │ │ └── useForm.ts │ │ ├── navigation │ │ │ ├── AppNavigator.tsx │ │ │ ├── DrawerNavigator.tsx │ │ │ ├── NavigationWrapper.tsx │ │ │ └── TabNavigator.tsx │ │ ├── screens │ │ │ ├── AboutUs.tsx │ │ │ ├── HomeScreen.js │ │ │ ├── HomeScreen.tsx │ │ │ ├── PostLogin │ │ │ │ ├── AboutUs │ │ │ │ │ ├── AboutUsScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── BluestoneAppsAI │ │ │ │ │ ├── BluestoneAppsAIScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── Calendar │ │ │ │ │ ├── CalendarScreen.tsx │ │ │ │ │ ├── EventDetails.tsx │ │ │ │ │ ├── EventDetailsStyles.ts │ │ │ │ │ └── Styles.ts │ │ │ │ ├── ChangePassword │ │ │ │ │ ├── ChangePasswordScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── Contact │ │ │ │ │ ├── ContactScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── CustomerSupport │ │ │ │ │ ├── CustomerSupportScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── EditProfile │ │ │ │ │ ├── EditProfileScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ ├── Home │ │ │ │ │ └── HomeScreen.tsx │ │ │ │ ├── MyProfile │ │ │ │ │ ├── MyProfileScreen.tsx │ │ │ │ │ └── Styles.ts │ │ │ │ └── Posts │ │ │ │ ├── PostScreen.tsx │ │ │ │ ├── PostsScreen.tsx │ │ │ │ ├── PostStyles.ts │ │ │ │ └── Styles.ts │ │ │ └── PreLogin │ │ │ ├── ForgotPassword │ │ │ │ ├── ForgotPasswordScreen.tsx │ │ │ │ └── Styles.ts │ │ │ ├── Legal │ │ │ │ ├── PrivacyPolicyScreen.tsx │ │ │ │ └── TermsAndConditionsScreen.tsx │ │ │ ├── Login │ │ │ │ ├── LoginScreen.tsx │ │ │ │ └── Styles.tsx │ │ │ ├── SignUp │ │ │ │ ├── SignUpScreen.tsx │ │ │ │ └── Styles.ts │ │ │ └── VerifyEmail │ │ │ ├── Styles.ts │ │ │ └── VerifyEmailScreen.tsx │ │ ├── services │ │ │ ├── apiService.ts │ │ │ ├── authService.ts │ │ │ ├── axiosRequest.ts │ │ │ ├── config.ts │ │ │ ├── NavigationMonitorService.ts │ │ │ ├── storageService.ts │ │ │ ├── types.ts │ │ │ ├── UpdateService.ts │ │ │ └── userService.ts │ │ ├── theme │ │ │ ├── colors_original.ts │ │ │ ├── colors.ts │ │ │ ├── README.md │ │ │ ├── theme.ts │ │ │ └── typography.ts │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── react-native.d.ts │ │ └── utils │ │ ├── axiosUtils.ts │ │ └── LogSuppressor.ts │ └── standards │ ├── api_communication.md │ ├── component_design.md │ ├── project_structure.md │ └── state_management.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /resources/code-examples/react-native/services/UpdateService.ts: -------------------------------------------------------------------------------- ```typescript import { Platform, Linking } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import axios from 'axios'; import { API } from '../config/apiConfig'; import DeviceInfo from 'react-native-device-info'; import { compareVersions } from 'compare-versions'; // Storage keys const STORAGE_KEYS = { SKIPPED_VERSIONS: 'skipped_versions', CURRENT_VERSION: 'current_version', }; // App store URLs and IDs const APP_STORE = { IOS_URL: 'https://apps.apple.com/us/app/id6670172327', IOS_LOOKUP_URL: 'https://itunes.apple.com/lookup?id=6670172327', ANDROID_URL: 'https://play.google.com/store/apps/details?id=your.package.name', }; interface VersionData { latestVersion: string; minimumVersion: string; releaseNotes?: string; } class UpdateService { /** * Get the current app version * @returns Promise resolving to the current app version */ static async getCurrentVersion(): Promise<string> { try { return DeviceInfo.getVersion(); } catch (error) { console.error('Error getting current version:', error); return '1.0.0'; // Fallback version } } /** * Get the stored app version * @returns Promise resolving to the stored app version or null if not found */ static async getStoredVersion(): Promise<string | null> { try { return await AsyncStorage.getItem(STORAGE_KEYS.CURRENT_VERSION); } catch (error) { console.error('Error getting stored version:', error); return null; } } /** * Update the stored app version * @param version The version to store * @returns Promise resolving when the version is stored */ static async updateStoredVersion(version: string): Promise<void> { try { await AsyncStorage.setItem(STORAGE_KEYS.CURRENT_VERSION, version); } catch (error) { console.error('Error updating stored version:', error); } } /** * Check for updates from the server * @returns Promise resolving to the version data */ static async checkForUpdates(): Promise<VersionData | null> { try { // First try to get the version from the App Store if on iOS if (Platform.OS === 'ios') { const appStoreVersion = await this.fetchAppStoreVersion(); if (appStoreVersion) { return { latestVersion: appStoreVersion, minimumVersion: appStoreVersion, // Assuming minimum version is the same as latest for App Store releaseNotes: 'A new version is available in the App Store.', }; } } // If App Store check fails or we're not on iOS, fall back to API check // Add a timeout to prevent long waits if the server is unresponsive const response = await axios.get(`${API.BASE_URL}/${API.ENDPOINTS.APP_VERSION}`, { timeout: 5000, // 5 second timeout }); if (response.data && response.data.success) { return { latestVersion: response.data.data.latestVersion || '1.0.0', minimumVersion: response.data.data.minimumVersion || '1.0.0', releaseNotes: response.data.data.releaseNotes || '', }; } // Return a default version info instead of null return await this.getDefaultVersionInfo(); } catch (error) { console.error('Error checking for updates'); // Instead of just logging the error, provide a fallback return await this.getDefaultVersionInfo(); } } /** * Fetch the latest version from the App Store * @returns Promise resolving to the App Store version or null if not found */ static async fetchAppStoreVersion(): Promise<string | null> { try { const response = await axios.get(APP_STORE.IOS_LOOKUP_URL, { timeout: 5000, // 5 second timeout }); if (response.data && response.data.resultCount > 0) { const appStoreVersion = response.data.results[0].version; return appStoreVersion; } return null; } catch (error) { console.error('Error fetching app version from App Store'); return null; } } /** * Get default version information when the server is unavailable * @returns Default version data */ private static async getDefaultVersionInfo(): Promise<VersionData> { // Get the current version from the app const currentVersion = await this.getCurrentVersion(); return { latestVersion: currentVersion, // Use current version as latest if we can't fetch it minimumVersion: '1.0.0', // Use a safe default for minimum version releaseNotes: 'Unable to fetch update information. Please check your internet connection.', }; } /** * Check if an update is required * @param currentVersion The current app version * @param minimumVersion The minimum required version * @returns True if an update is required, false otherwise */ static isUpdateRequired(currentVersion: string, minimumVersion: string): boolean { try { return compareVersions(currentVersion, minimumVersion) < 0; } catch (error) { console.error('Error checking if update is required'); return false; // Default to not required if there's an error } } /** * Check if the update modal should be shown * @param currentVersion Current app version * @param latestVersion Latest available version * @param minimumRequiredVersion Minimum required version * @returns Boolean indicating if the update modal should be shown */ static shouldShowUpdateModal( currentVersion: string, latestVersion: string, minimumRequiredVersion: string ): boolean { // Check if update is required (current version is less than minimum required) const isRequired = this.isUpdateRequired(currentVersion, minimumRequiredVersion); // Check if update is available (current version is less than latest version) const isUpdateAvailable = compareVersions(currentVersion, latestVersion) < 0; // Only show modal if update is required or an update is available return isRequired || isUpdateAvailable; } /** * Skip a version * @param version The version to skip * @returns Promise resolving when the version is skipped */ static async skipVersion(version: string): Promise<void> { try { // Get the current skipped versions const skippedVersionsString = await AsyncStorage.getItem(STORAGE_KEYS.SKIPPED_VERSIONS); const skippedVersions = skippedVersionsString ? JSON.parse(skippedVersionsString) : []; // Add the version if it's not already skipped if (!skippedVersions.includes(version)) { skippedVersions.push(version); await AsyncStorage.setItem(STORAGE_KEYS.SKIPPED_VERSIONS, JSON.stringify(skippedVersions)); } } catch (error) { console.error('Error skipping version:', error); } } /** * Check if a version has been skipped * @param version The version to check * @returns Promise resolving to true if the version has been skipped, false otherwise */ static async isVersionSkipped(version: string): Promise<boolean> { try { const skippedVersionsString = await AsyncStorage.getItem(STORAGE_KEYS.SKIPPED_VERSIONS); const skippedVersions = skippedVersionsString ? JSON.parse(skippedVersionsString) : []; return skippedVersions.includes(version); } catch (error) { console.error('Error checking if version is skipped:', error); return false; } } /** * Open the app store * @returns Promise resolving when the app store is opened */ static async openAppStore(): Promise<void> { try { const url = Platform.OS === 'ios' ? APP_STORE.IOS_URL : APP_STORE.ANDROID_URL; const canOpen = await Linking.canOpenURL(url); if (canOpen) { await Linking.openURL(url); } else { console.warn(`Cannot open URL: ${url}`); } } catch (error) { console.error('Error opening app store:', error); } } /** * Check if the app is installed from the App Store * @returns Promise resolving to whether the app is from the App Store */ static async isAppStoreVersion(): Promise<boolean> { try { if (Platform.OS === 'ios') { // On iOS, we can use DeviceInfo to determine if the app is running from the App Store // This is a simplified approach - in a real app you might need a more robust check const bundleId = DeviceInfo.getBundleId(); const isTestFlight = await DeviceInfo.isEmulator(); // If it's not an emulator and has a valid bundle ID, it's likely from the App Store return !isTestFlight && !!bundleId; } // For Android, we would need a different approach return false; } catch (error) { console.error('Error determining app source:', error); return false; } } } export default UpdateService; ``` -------------------------------------------------------------------------------- /resources/standards/state_management.md: -------------------------------------------------------------------------------- ```markdown # State Management Standards BluestoneApps follows these standards for state management in React Native applications. ## State Management Strategy We use a pragmatic approach to state management with a focus on simplicity and maintainability: 1. **Local Component State**: For UI state that doesn't need to be shared 2. **Custom Hooks**: For reusable state logic 3. **AsyncStorage**: For persistent data 4. **Context API**: For state shared across components (when necessary) ## Local Component State Use React's `useState` and `useEffect` hooks for component-local state: ```typescript // Simple state with useState const [isLoading, setIsLoading] = useState<boolean>(false); const [data, setData] = useState<DataType | null>(null); // Side effects with useEffect useEffect(() => { const fetchData = async () => { try { setIsLoading(true); const result = await apiService.getData(); setData(result); } catch (error) { console.error('Error fetching data:', error); } finally { setIsLoading(false); } }; fetchData(); }, []); ``` ### When to use local state: - Form input values - UI states like open/closed, visible/hidden - Component-specific data loading states - Temporary data that doesn't need persistence ## Custom Hooks Extract reusable state logic into custom hooks: ```typescript // src/hooks/useAppUpdate.ts import { useState, useCallback, useEffect } from 'react'; import UpdateService from '../services/UpdateService'; import AsyncStorage from '@react-native-async-storage/async-storage'; interface VersionInfo { latestVersion: string; minimumVersion: string; releaseNotes?: string; } const useAppUpdate = (checkOnMount = true, updateCheckInterval = 60 * 60 * 1000) => { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null); const [updateModalVisible, setUpdateModalVisible] = useState(false); // Check for updates const checkForUpdates = useCallback(async () => { try { setLoading(true); setError(null); const info = await UpdateService.checkForUpdates(); setVersionInfo(info); // Show update modal if needed if (info && UpdateService.isUpdateRequired(info)) { setUpdateModalVisible(true); } // Store last check time await AsyncStorage.setItem('last_update_check', Date.now().toString()); return info; } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); return null; } finally { setLoading(false); } }, []); // Check for updates on mount if enabled useEffect(() => { if (checkOnMount) { checkForUpdates(); } }, [checkOnMount, checkForUpdates]); // Return state and functions return { loading, error, versionInfo, updateModalVisible, setUpdateModalVisible, checkForUpdates, }; }; export default useAppUpdate; ``` ### When to use custom hooks: - Reusable state logic across multiple components - Complex state management with multiple related states - API communication patterns - Feature-specific logic that combines multiple React hooks ## AsyncStorage for Persistence Use AsyncStorage for persisting data across app sessions: ```typescript // Example in authService.ts import AsyncStorage from '@react-native-async-storage/async-storage'; class AuthService { async login(email: string, password: string): Promise<LoginResponse> { try { const response = await axiosRequest.post<LoginResponse>( API.ENDPOINTS.LOGIN, { email, password } ); if (response?.loginInfo?.token) { // Store authentication data await AsyncStorage.setItem('userToken', response.loginInfo.token); await AsyncStorage.setItem('userData', JSON.stringify({ loginInfo: response.loginInfo })); } return response; } catch (error) { console.error('Login error:', error); throw error; } } async logout(): Promise<void> { try { // Remove stored data await AsyncStorage.multiRemove(['userToken', 'userData', 'rememberMe']); } catch (error) { console.error('Logout error:', error); throw error; } } async isLoggedIn(): Promise<boolean> { try { const token = await AsyncStorage.getItem('userToken'); return token !== null; } catch (error) { console.error('Error checking login status:', error); return false; } } } export default new AuthService(); ``` ### When to use AsyncStorage: - Authentication tokens - User preferences - App configuration - Cached data that should persist between app launches - Form data that should be saved if the app closes ## Context API (When Needed) For cases where state needs to be shared across multiple components, use the Context API: ```typescript // src/navigation/NavigationContext.tsx import React, { createContext, useState, useContext, ReactNode } from 'react'; interface NavigationContextType { currentScreen: string; setCurrentScreen: (screen: string) => void; previousScreen: string | null; navigateBack: () => void; } const NavigationContext = createContext<NavigationContextType | undefined>(undefined); export const NavigationProvider: React.FC<{children: ReactNode}> = ({ children }) => { const [currentScreen, setCurrentScreen] = useState<string>('Home'); const [previousScreen, setPreviousScreen] = useState<string | null>(null); const handleSetCurrentScreen = (screen: string) => { setPreviousScreen(currentScreen); setCurrentScreen(screen); }; const navigateBack = () => { if (previousScreen) { setCurrentScreen(previousScreen); setPreviousScreen(null); } }; return ( <NavigationContext.Provider value={{ currentScreen, setCurrentScreen: handleSetCurrentScreen, previousScreen, navigateBack }} > {children} </NavigationContext.Provider> ); }; export const useNavigation = () => { const context = useContext(NavigationContext); if (context === undefined) { throw new Error('useNavigation must be used within a NavigationProvider'); } return context; }; ``` ### When to use Context API: - Theme information - Authentication state (when needed across many components) - Navigation state - Localization data - Application-wide preferences ## Best Practices 1. **Keep state as local as possible** - Only lift state up when necessary 2. **Use TypeScript** - Define proper interfaces for all state 3. **Implement proper error handling** - Always handle errors in async operations 4. **Separate concerns** - Keep UI state separate from data state 5. **Optimize performance** - Use memoization techniques like `useMemo` and `useCallback` 6. **Consistent patterns** - Use similar patterns across the application 7. **Minimize state** - Only track what's necessary in state 8. **Document state management** - Add comments explaining complex state logic const selectUsers = (state) => state.users.data; const selectCurrentUserId = (state) => state.auth.user?.id; export const selectCurrentUser = createSelector( [selectUsers, selectCurrentUserId], (users, currentUserId) => { if (!users || !currentUserId) return null; return users.find(user => user.id === currentUserId); } ); export const selectActiveUsers = createSelector( [selectUsers], (users) => users.filter(user => user.isActive) ); ``` ## Persistence For persisting state across app restarts: ```javascript // src/store/index.js import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { configureStore } from '@reduxjs/toolkit'; import rootReducer from './reducers'; const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['auth', 'userPreferences'], // only these reducers will be persisted }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'], }, }), }); export const persistor = persistStore(store); ``` ## State Management Best Practices 1. **Single Source of Truth**: Store overlapping data in only one place 2. **Immutable Updates**: Always use immutable patterns for updating state 3. **Normalize Complex Data**: Use normalized state structure for relational data 4. **Minimal State**: Only store essential application state 5. **Separate UI State**: Keep UI state separate from domain/data state 6. **Smart/Dumb Components**: Use container/presentational pattern 7. **Consistent Pattern**: Choose specific patterns for specific state needs 8. **Avoid Prop Drilling**: Use Context or Redux instead of passing props deeply ``` -------------------------------------------------------------------------------- /resources/code-examples/react-native/screens/PostLogin/Calendar/CalendarScreen.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState, useEffect, useMemo } from 'react'; import { View, Text, ScrollView, TouchableOpacity } from 'react-native'; import { Calendar } from 'react-native-calendars'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { styles } from './Styles'; import axiosRequest from '../../../helper/axiosRequest'; import { API } from '../../../helper/config'; import axios from 'axios'; import { colors } from '../../../theme/colors'; interface CalendarEvent { event_id: string; event_title: string; event_content: string; event_date: string; event_to_time: string; event_from_time: string; event_street_address: string; event_apt_suite: string; event_city: string; event_state: string; event_zip: string; event_longitude: string | null; event_latitude: string | null; event_price: string | null; } interface MarkedDates { [date: string]: { marked?: boolean; selected?: boolean; dotColor?: string; }; } const CalendarScreen = ({ navigation }: any) => { const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); const [events, setEvents] = useState<CalendarEvent[]>([]); const [markedDates, setMarkedDates] = useState<MarkedDates>({}); const fetchEvents = async () => { try { // Try to get token from both possible storage locations let token = await AsyncStorage.getItem('userToken'); if (!token) { const userDataStr = await AsyncStorage.getItem('userData'); if (userDataStr) { const userData = JSON.parse(userDataStr); token = userData?.loginInfo?.token; } } if (!token) { console.error('No token found in either storage location'); return; } console.log('Fetching events with token'); const response = await axiosRequest.get(API.ENDPOINTS.GET_EVENTS, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, params: { month: new Date(selectedDate).getMonth() + 1, // Adding 1 because getMonth() returns 0-11 year: new Date(selectedDate).getFullYear() } }); console.log('Events API Response:', response); if (response?.data?.status === 'ok' && Array.isArray(response?.data?.listing)) { const eventsData = response.data.listing; console.log('Events found:', eventsData); setEvents(eventsData); // Mark dates that have events const marked: MarkedDates = {}; eventsData.forEach((event: CalendarEvent) => { const eventDate = event.event_date; marked[eventDate] = { marked: true, dotColor: colors.tertiary }; }); // Also mark the selected date const selectedDateKey = new Date(selectedDate).toISOString().split('T')[0]; marked[selectedDateKey] = { ...marked[selectedDateKey], selected: true, selectedColor: colors.primary // Add this to make selected date more visible }; setMarkedDates(marked); } else { console.warn('Unexpected response format:', response); setEvents([]); setMarkedDates({ [selectedDate]: { selected: true, selectedColor: colors.primary } }); } } catch (error) { console.error('Error fetching events:', error); if (axios.isAxiosError(error)) { console.error('Error details:', { status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url, method: error.config?.method, headers: error.config?.headers, params: error.config?.params, response: error.response?.data }); } setEvents([]); setMarkedDates({ [selectedDate]: { selected: true } }); } }; useEffect(() => { fetchEvents(); }, [selectedDate]); // Re-fetch when selected date changes useEffect(() => { console.log('Selected date changed to:', selectedDate); console.log('Current events:', events); console.log('Filtered events:', filteredEvents); }, [selectedDate, events, filteredEvents]); const onDayPress = (day: any) => { console.log('Day pressed - raw data:', day); const newSelectedDate = day.dateString; console.log('Setting new selected date:', newSelectedDate); // Update marked dates to include the new selection const newMarkedDates = { ...markedDates }; // Remove selected state from previous date const previousSelectedDateKey = new Date(selectedDate).toISOString().split('T')[0]; if (markedDates[previousSelectedDateKey]) { newMarkedDates[previousSelectedDateKey] = { ...markedDates[previousSelectedDateKey], selected: false }; // If the date only had selected: true, remove it entirely if (!newMarkedDates[previousSelectedDateKey].marked) { delete newMarkedDates[previousSelectedDateKey]; } } // Add selected state to new date newMarkedDates[newSelectedDate] = { ...markedDates[newSelectedDate], selected: true }; // Update states setMarkedDates(newMarkedDates); setSelectedDate(newSelectedDate); }; // Filter events for selected date const filteredEvents = useMemo(() => { console.log('Filtering events for date:', selectedDate); console.log('Available events:', events); const filtered = events.filter(event => event.event_date === selectedDate); console.log('Filtered events for selected date:', filtered); return filtered; }, [events, selectedDate]); const formatDate = (dateString: string) => { console.log('Formatting date:', dateString); try { const [year, month, day] = dateString.split('-').map(num => parseInt(num, 10)); const date = new Date(year, month - 1, day); // month is 0-based in Date constructor console.log('Constructed date:', date); return date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); } catch (error) { console.error('Error formatting date:', error); return dateString; } }; const formattedSelectedDate = useMemo(() => { return formatDate(selectedDate); }, [selectedDate]); useEffect(() => { console.log('Selected date state updated:', selectedDate); console.log('Formatted date:', formatDate(selectedDate)); }, [selectedDate]); const formatTime = (timeStr: string) => { const [hours, minutes] = timeStr.split(':'); const date = new Date(); date.setHours(parseInt(hours, 10)); date.setMinutes(parseInt(minutes, 10)); return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); }; return ( <View style={styles.container}> <Calendar style={styles.calendar} onDayPress={onDayPress} markedDates={markedDates} theme={{ selectedDayBackgroundColor: colors.secondary, selectedDayTextColor: colors.white, todayTextColor: colors.primary, dotColor: colors.danger, arrowColor: colors.dark, monthTextColor: colors.dark, textDayFontWeight: '300', textMonthFontWeight: 'bold', textDayHeaderFontWeight: '500', textDayColor: colors.tertiary, textDayHeaderColor: colors.secondary }} /> <View style={[styles.sectionHeader, { padding: 20, marginVertical: 15 }]}> <Text style={[styles.sectionTitle, { fontSize: 20, fontWeight: '600' }]}> Events for {formatDate(selectedDate)} </Text> </View> <ScrollView style={styles.eventsContainer}> {filteredEvents.length > 0 ? ( filteredEvents.map((event) => ( <TouchableOpacity key={event.event_id} style={styles.eventCard} onPress={() => navigation.navigate('EventDetails', { event })} > <Text style={styles.eventTitle}>{event.event_title}</Text> <Text style={styles.eventTime}> {formatTime(event.event_from_time)} - {formatTime(event.event_to_time)} </Text> <Text style={styles.eventLocation}> {event.event_street_address} {event.event_apt_suite ? `, ${event.event_apt_suite}` : ''} {event.event_city ? `, ${event.event_city}` : ''} {event.event_state ? `, ${event.event_state}` : ''} {event.event_zip ? ` ${event.event_zip}` : ''} </Text> </TouchableOpacity> )) ) : ( <Text style={styles.noEventsText}>No events on this date</Text> )} </ScrollView> </View> ); }; export default CalendarScreen; ``` -------------------------------------------------------------------------------- /resources/code-examples/react-native/navigation/DrawerNavigator.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react'; import { withNavigationWrapper } from './NavigationWrapper'; import { View, StyleSheet, Platform, TouchableOpacity } from 'react-native'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import Icon from 'react-native-vector-icons/Ionicons'; import authService from '../services/authService'; import TabNavigator from './TabNavigator'; import AboutUsScreen from '../screens/PostLogin/AboutUs/AboutUsScreen'; import CalendarScreen from '../screens/PostLogin/Calendar/CalendarScreen'; import EventDetails from '../screens/PostLogin/Calendar/EventDetails'; import MyProfileScreen from '../screens/PostLogin/MyProfile/MyProfileScreen'; import EditProfileScreen from '../screens/PostLogin/EditProfile/EditProfileScreen'; import ChangePasswordScreen from '../screens/PostLogin/ChangePassword/ChangePasswordScreen'; import BluestoneAppsAIScreen from '../screens/PostLogin/BluestoneAppsAI/BluestoneAppsAIScreen'; import HomeScreen from '../screens/PostLogin/Home/HomeScreen'; import ContactScreen from '../screens/PostLogin/Contact/ContactScreen'; import PostsScreen from '../screens/PostLogin/Posts/PostsScreen'; import PostScreen from '../screens/PostLogin/Posts/PostScreen'; import { colors } from '../theme/colors'; const Drawer = createDrawerNavigator(); // Create a wrapper component that combines screen content with TabNavigator const ScreenWrapper = ({ children, navigation }: { children: React.ReactNode; navigation: any }) => { return ( <View style={styles.container}> <View style={styles.contentWrapper}> {React.cloneElement(children as React.ReactElement, { navigation })} </View> <View style={styles.tabNavigator}> <TabNavigator /> </View> </View> ); }; // Create screen-specific wrappers const AboutUsWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <AboutUsScreen /> </ScreenWrapper> )); const HomeWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <HomeScreen /> </ScreenWrapper> )); const CalendarWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <CalendarScreen /> </ScreenWrapper> )); const EventDetailsWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <EventDetails /> </ScreenWrapper> )); const ProfileWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <MyProfileScreen /> </ScreenWrapper> )); const EditProfileWrapper = withNavigationWrapper(({ navigation }: any) => ( <View style={styles.container}> <EditProfileScreen navigation={navigation} /> </View> )); const ChangePasswordWrapper = withNavigationWrapper(({ navigation }: any) => ( <View style={styles.container}> <ChangePasswordScreen navigation={navigation} /> </View> )); const AIWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <BluestoneAppsAIScreen /> </ScreenWrapper> )); const ContactWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <ContactScreen /> </ScreenWrapper> )); const PostWrapper = withNavigationWrapper(({ navigation }: any) => ( <ScreenWrapper navigation={navigation}> <PostScreen /> </ScreenWrapper> )); const DrawerNavigator = ({ navigation }: any) => { const handleLogout = async () => { try { // Call the logout service await authService.logout(); } catch (error) { console.error('Logout error:', error); } finally { // Always navigate to login screen, even if there was an error in logout navigation.reset({ index: 0, routes: [{ name: 'Login' }], }); } }; return ( <GestureHandlerRootView style={{ flex: 1 }}> <Drawer.Navigator screenOptions={{ headerShown: true, headerStyle: { backgroundColor: colors.headerBg, elevation: 0, shadowOpacity: 0, borderBottomWidth: 1, borderBottomColor: colors.light, }, headerTintColor: colors.headerFont, headerTitleStyle: { fontWeight: '600', color: colors.headerFont, }, drawerStyle: { backgroundColor: colors.white, width: 280, }, drawerActiveBackgroundColor: colors.footerBg, drawerActiveTintColor: colors.footerFont, drawerInactiveTintColor: colors.dark, }} > <Drawer.Screen name="Home" component={HomeWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="home-outline" size={24} color={color} /> ), }} /> <Drawer.Screen name="MyProfile" component={ProfileWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="person-outline" size={24} color={color} /> ), drawerLabel: 'My Profile', }} /> {/* Temporarily hidden Bluestone AI screen */} {/* <Drawer.Screen name="BluestoneAI" component={AIWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="bulb-outline" size={24} color={color} /> ), drawerLabel: 'Bluestone AI', }} /> */} <Drawer.Screen name="Calendar" component={CalendarWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="calendar-outline" size={24} color={color} /> ), drawerLabel: 'Calendar', }} /> <Drawer.Screen name="EventDetails" component={EventDetailsWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="calendar-outline" size={24} color={color} /> ), drawerLabel: () => null, drawerItemStyle: { display: 'none' }, headerLeft: () => ( <Icon name="chevron-back" size={28} color={colors.headerFont} style={{ marginLeft: 15 }} onPress={() => navigation.navigate('Calendar')} /> ), }} /> <Drawer.Screen name="About Us" component={AboutUsWrapper} options={{ drawerIcon: ({ focused, size, color }) => ( <Icon name={focused ? 'information-circle' : 'information-circle-outline'} size={size} color={focused ? color : '#666'} /> ), }} /> <Drawer.Screen name="Posts" component={PostsScreen} options={{ drawerIcon: ({ color }) => ( <Icon name="newspaper-outline" size={24} color={color} /> ), drawerLabel: 'Posts', }} /> <Drawer.Screen name="Post" component={PostWrapper} options={{ drawerItemStyle: { display: 'none' }, headerLeft: () => ( <Icon name="chevron-back" size={28} color={colors.headerFont} style={{ marginLeft: 15 }} onPress={() => navigation.navigate('Posts')} /> ), headerStyle: { backgroundColor: colors.headerBg, elevation: 0, shadowOpacity: 0, borderBottomWidth: 1, borderBottomColor: colors.light, }, headerTitleStyle: { color: colors.headerFont, fontSize: 18, }, }} /> <Drawer.Screen name="EditProfile" component={EditProfileWrapper} options={{ drawerItemStyle: { display: 'none' }, headerTitle: 'Edit Profile' }} /> <Drawer.Screen name="ChangePassword" component={ChangePasswordWrapper} options={{ drawerItemStyle: { display: 'none' }, headerTitle: 'Change Password' }} /> <Drawer.Screen name="Contact" component={ContactWrapper} options={{ drawerIcon: ({ color }) => ( <Icon name="mail-outline" size={24} color={color} /> ), drawerLabel: 'Contact Us', headerTitle: 'Contact Us', }} /> <Drawer.Screen name="Logout" component={EmptyComponent} options={{ drawerIcon: ({ color }) => ( <Icon name="log-out-outline" size={24} color={color} /> ), }} listeners={{ drawerItemPress: () => handleLogout(), }} /> </Drawer.Navigator> </GestureHandlerRootView> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', }, contentWrapper: { flex: 1, marginBottom: Platform.select({ ios: 80, android: 60, }), }, content: { flex: 1, }, tabNavigator: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#e0e0e0', }, }); const EmptyComponent = () => null; export default DrawerNavigator; ``` -------------------------------------------------------------------------------- /resources/code-examples/react-native/screens/HomeScreen.js: -------------------------------------------------------------------------------- ```javascript /** * HomeScreen Component * * Main screen of the application displaying featured content, * recent activities, and navigation options. */ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image, FlatList, RefreshControl, StatusBar, SafeAreaView } from 'react-native'; import { useNavigation } from '@react-navigation/native'; // Import components import Button from '../components/Button'; import Card from '../components/Card'; import FeatureCarousel from '../components/FeatureCarousel'; import LoadingIndicator from '../components/LoadingIndicator'; // Import services and utilities import ApiService from '../services/ApiService'; import { useAuth } from '../contexts/AuthContext'; import { formatDate } from '../utils/dateUtils'; import theme from '../theme/theme'; const HomeScreen = () => { const navigation = useNavigation(); const { user } = useAuth(); // State management const [featuredItems, setFeaturedItems] = useState([]); const [recentActivities, setRecentActivities] = useState([]); const [recommendations, setRecommendations] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); // Load data on component mount useEffect(() => { fetchHomeData(); }, []); // Function to fetch all necessary data const fetchHomeData = async () => { try { setLoading(true); setError(null); // Fetch featured items const featuredData = await ApiService.get('/featured', {}, { withCache: true, cacheTTL: 10 * 60 * 1000 // 10 minutes }); // Fetch recent activity const activitiesData = await ApiService.get('/activities/recent'); // Fetch personalized recommendations if user is logged in let recommendationsData = []; if (user) { recommendationsData = await ApiService.get('/recommendations'); } // Update state with fetched data setFeaturedItems(featuredData.items || []); setRecentActivities(activitiesData.activities || []); setRecommendations(recommendationsData.items || []); } catch (err) { console.error('Error fetching home data:', err); setError('Unable to load content. Please try again later.'); } finally { setLoading(false); setRefreshing(false); } }; // Pull-to-refresh handler const onRefresh = () => { setRefreshing(true); fetchHomeData(); }; // Navigate to item details const handleItemPress = (item) => { navigation.navigate('ItemDetails', { itemId: item.id }); }; // Render activity item const renderActivityItem = ({ item }) => ( <TouchableOpacity style={styles.activityItem} onPress={() => navigation.navigate('ActivityDetails', { activityId: item.id })} > <Image source={{ uri: item.imageUrl }} style={styles.activityImage} /> <View style={styles.activityContent}> <Text style={styles.activityTitle} numberOfLines={1}> {item.title} </Text> <Text style={styles.activityMeta}> {formatDate(item.date)} • {item.category} </Text> </View> </TouchableOpacity> ); // Render recommendation item const renderRecommendationItem = ({ item }) => ( <Card style={styles.recommendationCard} onPress={() => handleItemPress(item)} > <Image source={{ uri: item.imageUrl }} style={styles.recommendationImage} /> <View style={styles.recommendationContent}> <Text style={styles.recommendationTitle} numberOfLines={2}> {item.title} </Text> <Text style={styles.recommendationDescription} numberOfLines={3}> {item.description} </Text> </View> </Card> ); // Loading state if (loading && !refreshing) { return <LoadingIndicator fullScreen />; } return ( <SafeAreaView style={styles.safeArea}> <StatusBar barStyle="dark-content" /> <ScrollView style={styles.container} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[theme.colors.primary]} /> } > {/* Welcome section */} <View style={styles.welcomeSection}> <Text style={styles.welcomeTitle}> {user ? `Welcome back, ${user.firstName}!` : 'Welcome to AppName'} </Text> <Text style={styles.welcomeSubtitle}> Discover what's new today </Text> </View> {/* Featured carousel */} {featuredItems.length > 0 ? ( <View style={styles.carouselContainer}> <FeatureCarousel items={featuredItems} onItemPress={handleItemPress} /> </View> ) : null} {/* Quick actions */} <View style={styles.quickActions}> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Search')} > <Image source={require('../assets/icons/search.png')} style={styles.actionIcon} /> <Text style={styles.actionText}>Search</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Categories')} > <Image source={require('../assets/icons/categories.png')} style={styles.actionIcon} /> <Text style={styles.actionText}>Categories</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Favorites')} > <Image source={require('../assets/icons/favorite.png')} style={styles.actionIcon} /> <Text style={styles.actionText}>Favorites</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Notifications')} > <Image source={require('../assets/icons/notification.png')} style={styles.actionIcon} /> <Text style={styles.actionText}>Updates</Text> </TouchableOpacity> </View> {/* Recent activity section */} {recentActivities.length > 0 && ( <View style={styles.section}> <View style={styles.sectionHeader}> <Text style={styles.sectionTitle}>Recent Activity</Text> <TouchableOpacity onPress={() => navigation.navigate('AllActivities')}> <Text style={styles.seeAllText}>See All</Text> </TouchableOpacity> </View> <FlatList data={recentActivities} renderItem={renderActivityItem} keyExtractor={(item) => item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.activitiesList} /> </View> )} {/* Recommendations section (only for logged in users) */} {user && recommendations.length > 0 && ( <View style={styles.section}> <View style={styles.sectionHeader}> <Text style={styles.sectionTitle}>Recommended for You</Text> <TouchableOpacity onPress={() => navigation.navigate('Recommendations')}> <Text style={styles.seeAllText}>See All</Text> </TouchableOpacity> </View> <FlatList data={recommendations} renderItem={renderRecommendationItem} keyExtractor={(item) => item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.recommendationsList} /> </View> )} {/* Call to action */} <View style={styles.ctaContainer}> <Image source={require('../assets/images/cta-background.png')} style={styles.ctaBackground} /> <View style={styles.ctaContent}> <Text style={styles.ctaTitle}>Ready to get started?</Text> <Text style={styles.ctaDescription}> Join thousands of users and start exploring now. </Text> <Button title={user ? "Explore Premium" : "Sign Up Now"} onPress={() => navigation.navigate(user ? 'Subscription' : 'Signup')} variant="primary" style={styles.ctaButton} /> </View> </View> {/* Error message */} {error && ( <View style={styles.errorContainer}> <Text style={styles.errorText}>{error}</Text> <Button title="Try Again" onPress={fetchHomeData} variant="outline" size="small" style={styles.retryButton} /> </View> )} {/* Bottom padding */} <View style={styles.bottomPadding} /> </ScrollView> </SafeAreaView> ); }; const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: theme.colors.background, }, container: { flex: 1, }, welcomeSection: { padding: theme.spacing.lg, }, welcomeTitle: { ...theme.typography.h4, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, welcomeSubtitle: { ...theme.typography.body, color: theme.colors.textSecondary, }, carouselContainer: { marginBottom: theme.spacing.lg, }, quickActions: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: theme.spacing.lg, marginBottom: theme.spacing.xl, }, actionButton: { alignItems: 'center', width: 70, }, actionIcon: { width: 40, height: 40, marginBottom: theme.spacing.xs, }, actionText: { ...theme.typography.caption, color: theme.colors.textPrimary, }, section: { marginBottom: theme.spacing.xl, }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: theme.spacing.lg, marginBottom: theme.spacing.md, }, sectionTitle: { ...theme.typography.h5, color: theme.colors.textPrimary, }, seeAllText: { ...theme.typography.body, color: theme.colors.primary, }, activitiesList: { paddingLeft: theme.spacing.lg, }, activityItem: { width: 280, marginRight: theme.spacing.md, borderRadius: theme.borderRadius.md, backgroundColor: theme.colors.white, ...theme.shadows.sm, overflow: 'hidden', }, activityImage: { width: '100%', height: 150, resizeMode: 'cover', }, activityContent: { padding: theme.spacing.md, }, activityTitle: { ...theme.typography.h6, marginBottom: theme.spacing.xs, }, activityMeta: { ...theme.typography.caption, color: theme.colors.textSecondary, }, recommendationsList: { paddingLeft: theme.spacing.lg, }, recommendationCard: { width: 200, marginRight: theme.spacing.md, overflow: 'hidden', }, recommendationImage: { width: '100%', height: 120, resizeMode: 'cover', }, recommendationContent: { padding: theme.spacing.md, }, recommendationTitle: { ...theme.typography.h6, fontSize: 15, marginBottom: theme.spacing.xs, }, recommendationDescription: { ...theme.typography.caption, color: theme.colors.textSecondary, }, ctaContainer: { marginHorizontal: theme.spacing.lg, marginBottom: theme.spacing.xl, borderRadius: theme.borderRadius.lg, overflow: 'hidden', position: 'relative', height: 180, }, ctaBackground: { position: 'absolute', width: '100%', height: '100%', resizeMode: 'cover', }, ctaContent: { padding: theme.spacing.lg, backgroundColor: 'rgba(0,0,0,0.5)', height: '100%', justifyContent: 'center', }, ctaTitle: { ...theme.typography.h4, color: theme.colors.white, marginBottom: theme.spacing.sm, }, ctaDescription: { ...theme.typography.body, color: theme.colors.white, marginBottom: theme.spacing.lg, }, ctaButton: { alignSelf: 'flex-start', }, errorContainer: { margin: theme.spacing.lg, padding: theme.spacing.lg, backgroundColor: theme.colors.errorLight, borderRadius: theme.borderRadius.md, alignItems: 'center', }, errorText: { ...theme.typography.body, color: theme.colors.error, marginBottom: theme.spacing.md, textAlign: 'center', }, retryButton: { marginTop: theme.spacing.sm, }, bottomPadding: { height: 40, }, }); export default HomeScreen; ``` -------------------------------------------------------------------------------- /resources/code-examples/react-native/screens/HomeScreen.tsx: -------------------------------------------------------------------------------- ```typescript /** * HomeScreen Component * * Main screen of the application displaying featured content, * recent activities, and navigation options. */ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image, FlatList, RefreshControl, StatusBar, SafeAreaView, ImageSourcePropType } from 'react-native'; import { useNavigation } from '@react-navigation/native'; // Import components import Button from '../components/Button'; import Card from '../components/Card'; import FeatureCarousel from '../components/FeatureCarousel'; import LoadingIndicator from '../components/LoadingIndicator'; // Import services and utilities import ApiService from '../services/ApiService'; import { useAuth } from '../contexts/AuthContext'; import { formatDate } from '../utils/dateUtils'; import theme from '../theme/theme'; // Define interfaces for data types interface FeaturedItem { id: string; title: string; description: string; imageUrl: string; } interface Activity { id: string; title: string; date: string; category: string; imageUrl: string; } interface Recommendation { id: string; title: string; description: string; imageUrl: string; } interface User { firstName: string; } const HomeScreen: React.FC = () => { const navigation = useNavigation(); const { user } = useAuth() as { user: User | null }; // State management const [featuredItems, setFeaturedItems] = useState<FeaturedItem[]>([]); const [recentActivities, setRecentActivities] = useState<Activity[]>([]); const [recommendations, setRecommendations] = useState<Recommendation[]>([]); const [loading, setLoading] = useState<boolean>(true); const [refreshing, setRefreshing] = useState<boolean>(false); const [error, setError] = useState<string | null>(null); // Load data on component mount useEffect(() => { fetchHomeData(); }, []); // Function to fetch all necessary data const fetchHomeData = async (): Promise<void> => { try { setLoading(true); setError(null); // Fetch featured items const featuredData = await ApiService.get('/featured', {}, { withCache: true, cacheTTL: 10 * 60 * 1000 // 10 minutes }); // Fetch recent activity const activitiesData = await ApiService.get('/activities/recent'); // Fetch personalized recommendations if user is logged in let recommendationsData = { items: [] }; if (user) { recommendationsData = await ApiService.get('/recommendations'); } // Update state with fetched data setFeaturedItems(featuredData.items || []); setRecentActivities(activitiesData.activities || []); setRecommendations(recommendationsData.items || []); } catch (err) { console.error('Error fetching home data:', err); setError('Unable to load content. Please try again later.'); } finally { setLoading(false); setRefreshing(false); } }; // Pull-to-refresh handler const onRefresh = (): void => { setRefreshing(true); fetchHomeData(); }; // Navigate to item details const handleItemPress = (item: FeaturedItem | Recommendation): void => { navigation.navigate('ItemDetails' as never, { itemId: item.id } as never); }; // Render activity item const renderActivityItem = ({ item }: { item: Activity }): React.ReactElement => ( <TouchableOpacity style={styles.activityItem} onPress={() => navigation.navigate('ActivityDetails' as never, { activityId: item.id } as never)} > <Image source={{ uri: item.imageUrl }} style={styles.activityImage} /> <View style={styles.activityContent}> <Text style={styles.activityTitle} numberOfLines={1}> {item.title} </Text> <Text style={styles.activityMeta}> {formatDate(item.date)} • {item.category} </Text> </View> </TouchableOpacity> ); // Render recommendation item const renderRecommendationItem = ({ item }: { item: Recommendation }): React.ReactElement => ( <Card style={styles.recommendationCard} onPress={() => handleItemPress(item)} > <Image source={{ uri: item.imageUrl }} style={styles.recommendationImage} /> <View style={styles.recommendationContent}> <Text style={styles.recommendationTitle} numberOfLines={2}> {item.title} </Text> <Text style={styles.recommendationDescription} numberOfLines={3}> {item.description} </Text> </View> </Card> ); // Loading state if (loading && !refreshing) { return <LoadingIndicator fullScreen />; } return ( <SafeAreaView style={styles.safeArea}> <StatusBar barStyle="dark-content" /> <ScrollView style={styles.container} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[theme.colors.primary]} /> } > {/* Welcome section */} <View style={styles.welcomeSection}> <Text style={styles.welcomeTitle}> {user ? `Welcome back, ${user.firstName}!` : 'Welcome to AppName'} </Text> <Text style={styles.welcomeSubtitle}> Discover what's new today </Text> </View> {/* Featured carousel */} {featuredItems.length > 0 ? ( <View style={styles.carouselContainer}> <FeatureCarousel items={featuredItems} onItemPress={handleItemPress} /> </View> ) : null} {/* Quick actions */} <View style={styles.quickActions}> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Search' as never)} > <Image source={require('../assets/icons/search.png') as ImageSourcePropType} style={styles.actionIcon} /> <Text style={styles.actionText}>Search</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Categories' as never)} > <Image source={require('../assets/icons/categories.png') as ImageSourcePropType} style={styles.actionIcon} /> <Text style={styles.actionText}>Categories</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Favorites' as never)} > <Image source={require('../assets/icons/favorite.png') as ImageSourcePropType} style={styles.actionIcon} /> <Text style={styles.actionText}>Favorites</Text> </TouchableOpacity> <TouchableOpacity style={styles.actionButton} onPress={() => navigation.navigate('Notifications' as never)} > <Image source={require('../assets/icons/notification.png') as ImageSourcePropType} style={styles.actionIcon} /> <Text style={styles.actionText}>Updates</Text> </TouchableOpacity> </View> {/* Recent activity section */} {recentActivities.length > 0 && ( <View style={styles.section}> <View style={styles.sectionHeader}> <Text style={styles.sectionTitle}>Recent Activity</Text> <TouchableOpacity onPress={() => navigation.navigate('AllActivities' as never)}> <Text style={styles.seeAllText}>See All</Text> </TouchableOpacity> </View> <FlatList data={recentActivities} renderItem={renderActivityItem} keyExtractor={(item) => item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.activitiesList} /> </View> )} {/* Recommendations section (only for logged in users) */} {user && recommendations.length > 0 && ( <View style={styles.section}> <View style={styles.sectionHeader}> <Text style={styles.sectionTitle}>Recommended for You</Text> <TouchableOpacity onPress={() => navigation.navigate('Recommendations' as never)}> <Text style={styles.seeAllText}>See All</Text> </TouchableOpacity> </View> <FlatList data={recommendations} renderItem={renderRecommendationItem} keyExtractor={(item) => item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.recommendationsList} /> </View> )} {/* Call to action */} <View style={styles.ctaContainer}> <Image source={require('../assets/images/cta-background.png') as ImageSourcePropType} style={styles.ctaBackground} /> <View style={styles.ctaContent}> <Text style={styles.ctaTitle}>Ready to get started?</Text> <Text style={styles.ctaDescription}> Join thousands of users and start exploring now. </Text> <Button title={user ? "Explore Premium" : "Sign Up Now"} onPress={() => navigation.navigate(user ? 'Subscription' as never : 'Signup' as never)} style={styles.ctaButton} /> </View> </View> {/* Error message */} {error && ( <View style={styles.errorContainer}> <Text style={styles.errorText}>{error}</Text> <Button title="Try Again" onPress={fetchHomeData} style={styles.retryButton} /> </View> )} {/* Bottom padding */} <View style={styles.bottomPadding} /> </ScrollView> </SafeAreaView> ); }; const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: theme.colors.background, }, container: { flex: 1, }, welcomeSection: { padding: theme.spacing.lg, }, welcomeTitle: { ...theme.typography.h4, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, welcomeSubtitle: { ...theme.typography.body, color: theme.colors.textSecondary, }, carouselContainer: { marginBottom: theme.spacing.lg, }, quickActions: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: theme.spacing.lg, marginBottom: theme.spacing.xl, }, actionButton: { alignItems: 'center', width: 70, }, actionIcon: { width: 40, height: 40, marginBottom: theme.spacing.xs, }, actionText: { ...theme.typography.caption, color: theme.colors.textPrimary, }, section: { marginBottom: theme.spacing.xl, }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: theme.spacing.lg, marginBottom: theme.spacing.md, }, sectionTitle: { ...theme.typography.h5, color: theme.colors.textPrimary, }, seeAllText: { ...theme.typography.body, color: theme.colors.primary, }, activitiesList: { paddingLeft: theme.spacing.lg, }, activityItem: { width: 280, marginRight: theme.spacing.md, borderRadius: theme.borderRadius.md, backgroundColor: theme.colors.white, ...theme.shadows.sm, overflow: 'hidden', }, activityImage: { width: '100%', height: 150, resizeMode: 'cover', }, activityContent: { padding: theme.spacing.md, }, activityTitle: { ...theme.typography.h6, marginBottom: theme.spacing.xs, }, activityMeta: { ...theme.typography.caption, color: theme.colors.textSecondary, }, recommendationsList: { paddingLeft: theme.spacing.lg, }, recommendationCard: { width: 200, marginRight: theme.spacing.md, overflow: 'hidden', }, recommendationImage: { width: '100%', height: 120, resizeMode: 'cover', }, recommendationContent: { padding: theme.spacing.md, }, recommendationTitle: { ...theme.typography.h6, fontSize: 15, marginBottom: theme.spacing.xs, }, recommendationDescription: { ...theme.typography.caption, color: theme.colors.textSecondary, }, ctaContainer: { marginHorizontal: theme.spacing.lg, marginBottom: theme.spacing.xl, borderRadius: theme.borderRadius.lg, overflow: 'hidden', position: 'relative', height: 180, }, ctaBackground: { position: 'absolute', width: '100%', height: '100%', resizeMode: 'cover', }, ctaContent: { padding: theme.spacing.lg, backgroundColor: 'rgba(0,0,0,0.5)', height: '100%', justifyContent: 'center', }, ctaTitle: { ...theme.typography.h4, color: theme.colors.white, marginBottom: theme.spacing.sm, }, ctaDescription: { ...theme.typography.body, color: theme.colors.white, marginBottom: theme.spacing.lg, }, ctaButton: { alignSelf: 'flex-start', }, errorContainer: { margin: theme.spacing.lg, padding: theme.spacing.lg, backgroundColor: theme.colors.errorLight, borderRadius: theme.borderRadius.md, alignItems: 'center', }, errorText: { ...theme.typography.body, color: theme.colors.error, marginBottom: theme.spacing.md, textAlign: 'center', }, retryButton: { marginTop: theme.spacing.sm, }, bottomPadding: { height: 40, }, }); export default HomeScreen; ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { glob } from 'glob'; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Base directory for resources const BASE_DIR = path.resolve(__dirname, '..'); const RESOURCES_DIR = path.join(BASE_DIR, "resources"); const CODE_EXAMPLES_DIR = path.join(RESOURCES_DIR, "code-examples"); // Create server instance const server = new McpServer({ name: "bluestoneapps", version: "0.2.1", capabilities: { tools: {}, }, }); // Helper function to get standard content function getStandardContent(category: string, standardId: string): { content?: string; error?: string } { const standardPath = path.join(RESOURCES_DIR, category, `${standardId}.md`); if (!fs.existsSync(standardPath)) { return { error: `Standard ${standardId} not found` }; } try { const content = fs.readFileSync(standardPath, 'utf8'); return { content }; } catch (err) { console.error(`Error reading standard ${standardId}:`, err); return { error: `Error reading standard ${standardId}` }; } } // Helper function to find file in subdirectories function findFileInSubdirectories(baseDir: string, fileName: string, extensions: string[] = ['.js', '.jsx', '.ts', '.tsx']) { // First, try with exact filename match let files = glob.sync(`${baseDir}/**/${fileName}`); if (files.length > 0) { return files[0]; } // Then try with file name + extensions for (const ext of extensions) { const fileWithExt = `${fileName}${ext}`; files = glob.sync(`${baseDir}/**/${fileWithExt}`); if (files.length > 0) { return files[0]; } } return null; } // Helper function to get example content function getExampleContent(subcategory: string, filename: string): { content?: string[]; path?: string; error?: string } { const searchDir = path.join(CODE_EXAMPLES_DIR, "react-native", subcategory); const filePath = findFileInSubdirectories(searchDir, filename); if (!filePath || !fs.existsSync(filePath)) { return { error: `Example ${filename} not found` }; } try { const content = fs.readFileSync(filePath, 'utf8'); return { content: [content], path: path.relative(BASE_DIR, filePath) }; } catch (err) { console.error(`Error reading example ${filename}:`, err); return { error: `Error reading example ${filename}` }; } } // Find closest match implementation function findClosestMatch(directory: string, searchName: string, extensions: string[] = ['.js', '.jsx', '.ts', '.tsx']) { if (!fs.existsSync(directory)) return null; let closestMatch = null; for (const ext of extensions) { const files = glob.sync(`${directory}/**/*${ext}`); for (const filePath of files) { const fileName = path.basename(filePath); const fileNameNoExt = path.basename(fileName, path.extname(fileName)); if (fileNameNoExt.toLowerCase().includes(searchName.toLowerCase())) { closestMatch = fileNameNoExt; break; } } if (closestMatch) break; } return closestMatch; } // List all available examples function listAvailableExamples() { const examples: Record<string, string[]> = { components: [], hooks: [], services: [], screens: [], themes: [] }; const categories = [ { key: "components", dir: "components" }, { key: "hooks", dir: "hooks" }, { key: "services", dir: "services" }, { key: "screens", dir: "screens" }, { key: "themes", dir: "theme" } ]; const extensions = ['.js', '.jsx', '.ts', '.tsx']; for (const category of categories) { const dirPath = path.join(CODE_EXAMPLES_DIR, "react-native", category.dir); if (fs.existsSync(dirPath)) { for (const ext of extensions) { const files = glob.sync(`${dirPath}/**/*${ext}`); for (const filePath of files) { const fileName = path.basename(filePath); const fileNameNoExt = path.basename(fileName, path.extname(fileName)); examples[category.key].push(fileNameNoExt); } } } } return examples; } // Register tools // 1. Get project structure server.tool( "get_project_structure", "Get project structure standards for React Native development", {}, async () => { const result = getStandardContent("standards", "project_structure"); return { content: [ { type: "text", text: result.content ?? result.error ?? "Error: No content or error message available", }, ], }; }, ); // 2. Get API communication server.tool( "get_api_communication", "Get API communication standards for React Native development", {}, async () => { const result = getStandardContent("standards", "api_communication"); return { content: [ { type: "text", text: result.content ?? result.error ?? "Error: No content or error message available", }, ], }; }, ); // 3. Get component design server.tool( "get_component_design", "Get component design standards for React Native development", {}, async () => { const result = getStandardContent("standards", "component_design"); return { content: [ { type: "text", text: result.content ?? result.error ?? "Error: No content or error message available", }, ], }; }, ); // 4. Get state management server.tool( "get_state_management", "Get state management standards for React Native development", {}, async () => { const result = getStandardContent("standards", "state_management"); return { content: [ { type: "text", text: result.content ?? result.error ?? "Error: No content or error message available", }, ], }; }, ); // 5. Get component example server.tool( "get_component_example", "Get a React Native component example", { component_name: z.string().describe("Component Name"), }, async ({ component_name }) => { if (!component_name) { return { content: [ { type: "text", text: "Component name not specified", }, ], }; } try { // First try exact match const result = getExampleContent("components", component_name); if (result.error) { // Try to find by fuzzy match const componentsDir = path.join(CODE_EXAMPLES_DIR, "react-native", "components"); const closestMatch = findClosestMatch(componentsDir, component_name); if (closestMatch) { const fuzzyResult = getExampleContent("components", closestMatch); return { content: [ { type: "text", text: fuzzyResult.content?.[0] ?? fuzzyResult.error ?? "Error: No content available", }, ], }; } else { return { content: [ { type: "text", text: `Component ${component_name} not found`, }, ], }; } } return { content: [ { type: "text", text: result.content?.[0] ?? result.error ?? "Error: No content available", }, ], }; } catch (err) { console.error(`Error getting component example ${component_name}:`, err); return { content: [ { type: "text", text: `Error getting component example: ${err}`, }, ], }; } }, ); // 6. Get hook example server.tool( "get_hook_example", "Get a React Native hook example", { hook_name: z.string().describe("Hook Name"), }, async ({ hook_name }) => { if (!hook_name) { return { content: [ { type: "text", text: "Hook name not specified", }, ], }; } try { // First try exact match const result = getExampleContent("hooks", hook_name); if (result.error) { // Try to find by fuzzy match const hooksDir = path.join(CODE_EXAMPLES_DIR, "react-native", "hooks"); const closestMatch = findClosestMatch(hooksDir, hook_name); if (closestMatch) { const fuzzyResult = getExampleContent("hooks", closestMatch); return { content: [ { type: "text", text: fuzzyResult.content?.[0] ?? fuzzyResult.error ?? "Error: No content available", }, ], }; } else { return { content: [ { type: "text", text: `Hook ${hook_name} not found`, }, ], }; } } return { content: [ { type: "text", text: result.content?.[0] ?? result.error ?? "Error: No content available", }, ], }; } catch (err) { console.error(`Error getting hook example ${hook_name}:`, err); return { content: [ { type: "text", text: `Error getting hook example: ${err}`, }, ], }; } }, ); // 7. Get service example server.tool( "get_service_example", "Get a React Native service example", { service_name: z.string().describe("Service Name"), }, async ({ service_name }) => { if (!service_name) { return { content: [ { type: "text", text: "Service name not specified", }, ], }; } try { // First try exact match const result = getExampleContent("services", service_name); if (result.error) { // Try to find by fuzzy match const servicesDir = path.join(CODE_EXAMPLES_DIR, "react-native", "services"); const closestMatch = findClosestMatch(servicesDir, service_name); if (closestMatch) { const fuzzyResult = getExampleContent("helper", closestMatch); return { content: [ { type: "text", text: fuzzyResult.content?.[0] ?? fuzzyResult.error ?? "Error: No content available", }, ], }; } else { return { content: [ { type: "text", text: `Service ${service_name} not found`, }, ], }; } } return { content: [ { type: "text", text: result.content?.[0] ?? result.error ?? "Error: No content available", }, ], }; } catch (err) { console.error(`Error getting service example ${service_name}:`, err); return { content: [ { type: "text", text: `Error getting service example: ${err}`, }, ], }; } }, ); // 8. Get screen example server.tool( "get_screen_example", "Get a React Native screen example", { screen_name: z.string().describe("Screen Name"), }, async ({ screen_name }) => { if (!screen_name) { return { content: [ { type: "text", text: "Screen name not specified", }, ], }; } try { // First try exact match const result = getExampleContent("screens", screen_name); if (result.error) { // Try to find by fuzzy match const screensDir = path.join(CODE_EXAMPLES_DIR, "react-native", "screens"); const closestMatch = findClosestMatch(screensDir, screen_name); if (closestMatch) { const fuzzyResult = getExampleContent("screens", closestMatch); return { content: [ { type: "text", text: fuzzyResult.content?.[0] ?? fuzzyResult.error ?? "Error: No content available", }, ], }; } else { return { content: [ { type: "text", text: `Screen ${screen_name} not found`, }, ], }; } } return { content: [ { type: "text", text: result.content?.[0] ?? result.error ?? "Error: No content available", }, ], }; } catch (err) { console.error(`Error getting screen example ${screen_name}:`, err); return { content: [ { type: "text", text: `Error getting screen example: ${err}`, }, ], }; } }, ); // 9. Get theme example server.tool( "get_theme_example", "Get code for a React Native theme", { theme_name: z.string().describe("Theme Name"), }, async ({ theme_name }) => { if (!theme_name) { return { content: [ { type: "text", text: "Theme name not specified", }, ], }; } try { // First try exact match const result = getExampleContent("theme", theme_name); if (result.error) { // Try to find by fuzzy match const themesDir = path.join(CODE_EXAMPLES_DIR, "react-native", "theme"); const closestMatch = findClosestMatch(themesDir, theme_name); if (closestMatch) { const fuzzyResult = getExampleContent("theme", closestMatch); return { content: [ { type: "text", text: fuzzyResult.content?.[0] ?? fuzzyResult.error ?? "Error: No content available", }, ], }; } else { return { content: [ { type: "text", text: `Theme ${theme_name} not found`, }, ], }; } } return { content: [ { type: "text", text: result.content?.[0] ?? result.error ?? "Error: No content available", }, ], }; } catch (err) { console.error(`Error getting theme example ${theme_name}:`, err); return { content: [ { type: "text", text: `Error getting theme example: ${err}`, }, ], }; } }, ); // 10. List available examples server.tool( "list_available_examples", "List all available code examples by category", {}, async () => { try { const examples = listAvailableExamples(); return { content: [ { type: "text", text: JSON.stringify(examples, null, 2), }, ], }; } catch (err) { console.error("Error listing available examples:", err); return { content: [ { type: "text", text: `Error listing available examples: ${err}`, }, ], }; } }, ); // Run the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("BluestoneApps MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); ```