Added admin page
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
# modules/admin/api.py
|
||||
from typing import Annotated
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Body # Import Body
|
||||
from pydantic import BaseModel # Import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from core.database import Base, get_db
|
||||
from modules.auth.models import User, UserRole
|
||||
@@ -9,29 +10,37 @@ from modules.auth.dependencies import admin_only
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"], dependencies=[Depends(admin_only)])
|
||||
|
||||
# Define a Pydantic model for the request body
|
||||
class ClearDbRequest(BaseModel):
|
||||
hard: bool
|
||||
|
||||
@router.get("/")
|
||||
def read_admin():
|
||||
return {"message": "Admin route"}
|
||||
|
||||
@router.get("/cleardb")
|
||||
def clear_db(db: Annotated[Session, Depends(get_db)], hard: bool):
|
||||
# Change to POST and use the request body model
|
||||
@router.post("/cleardb")
|
||||
def clear_db(payload: ClearDbRequest, db: Annotated[Session, Depends(get_db)]):
|
||||
"""
|
||||
Clear the database.
|
||||
'hard' parameter determines if the database should be completely reset.
|
||||
Clear the database based on the 'hard' flag in the request body.
|
||||
'hard'=True: Drop and recreate all tables.
|
||||
'hard'=False: Delete data from tables except users.
|
||||
"""
|
||||
hard = payload.hard # Get 'hard' from the payload
|
||||
if hard:
|
||||
# ... existing hard clear logic ...
|
||||
Base.metadata.drop_all(bind=db.get_bind())
|
||||
Base.metadata.create_all(bind=db.get_bind())
|
||||
db.commit()
|
||||
return {"message": "Database reset (HARD)"}
|
||||
else:
|
||||
# ... existing soft clear logic ...
|
||||
tables = Base.metadata.tables.keys()
|
||||
for table_name in tables:
|
||||
# delete all tables that isn't the users table
|
||||
if table_name != "users":
|
||||
table = Base.metadata.tables[table_name]
|
||||
print(f"Deleting table: {table_name}")
|
||||
db.execute(table.delete())
|
||||
|
||||
# delete all non-admin accounts
|
||||
db.query(User).filter(User.role != UserRole.ADMIN).delete()
|
||||
db.commit()
|
||||
return {"message": "Database cleared"}
|
||||
Binary file not shown.
16
interfaces/nativeapp/src/api/admin.ts
Normal file
16
interfaces/nativeapp/src/api/admin.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import apiClient from './client';
|
||||
|
||||
interface ClearDbResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const clearDatabase = async (hard: boolean): Promise<ClearDbResponse> => {
|
||||
try {
|
||||
const response = await apiClient.post<ClearDbResponse>('/admin/cleardb', { hard });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error calling clearDatabase API:', error);
|
||||
// Re-throw the error so the component can handle it
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -5,8 +5,8 @@ import * as SecureStore from 'expo-secure-store';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
|
||||
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.255.221:8000/api'; // Use your machine's IP
|
||||
// const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.1.9:8000/api'; // Use your machine's IP
|
||||
// const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.255.221:8000/api'; // Use your machine's IP
|
||||
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.1.9:8000/api'; // Use your machine's IP
|
||||
// const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:8000/api'; // Use your machine's IP
|
||||
const ACCESS_TOKEN_KEY = 'maia_access_token'; // Renamed for clarity
|
||||
const REFRESH_TOKEN_KEY = 'maia_refresh_token'; // Key for refresh token
|
||||
|
||||
@@ -4,26 +4,38 @@ import { View, StyleSheet, Pressable } from 'react-native';
|
||||
import { Text, useTheme, Icon } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { NavigationContainerRef } from '@react-navigation/native'; // Import ref type
|
||||
import { RootStackParamList } from '../types/navigation'; // Import stack param list
|
||||
import { WebContentStackParamList } from '../types/navigation'; // Use WebContentStackParamList
|
||||
import { useAuth, UserRole } from '../contexts/AuthContext'; // Import useAuth and UserRole
|
||||
|
||||
// Define Props including the navigation ref
|
||||
interface WebSidebarProps {
|
||||
navigationRef: React.RefObject<NavigationContainerRef<RootStackParamList>>;
|
||||
navigationRef: React.RefObject<NavigationContainerRef<WebContentStackParamList>>; // Use WebContentStackParamList
|
||||
currentRouteName?: string; // To highlight the active item
|
||||
}
|
||||
|
||||
// Define navigation items type
|
||||
type NavItem = {
|
||||
name: keyof WebContentStackParamList;
|
||||
icon: string;
|
||||
label: string;
|
||||
adminOnly?: boolean; // Add flag for admin-only items
|
||||
};
|
||||
|
||||
// Define navigation items
|
||||
const navItems = [
|
||||
const navItems: NavItem[] = [
|
||||
{ name: 'Dashboard', icon: 'view-dashboard', label: 'Dashboard' },
|
||||
{ name: 'Chat', icon: 'chat', label: 'Chat' },
|
||||
{ name: 'Calendar', icon: 'calendar', label: 'Calendar' },
|
||||
{ name: 'Profile', icon: 'account-circle', label: 'Profile' },
|
||||
] as const; // Use 'as const' for stricter typing of names
|
||||
{ name: 'Admin', icon: 'shield-crown', label: 'Admin', adminOnly: true }, // Add Admin item
|
||||
];
|
||||
|
||||
const WebSidebar = ({ navigationRef, currentRouteName }: WebSidebarProps) => {
|
||||
const theme = useTheme();
|
||||
const { user } = useAuth(); // Get user data
|
||||
const isAdmin = user?.role === UserRole.ADMIN;
|
||||
|
||||
const handleNavigate = (screenName: keyof RootStackParamList) => {
|
||||
const handleNavigate = (screenName: keyof WebContentStackParamList) => {
|
||||
// Use the ref to navigate
|
||||
if (navigationRef.current) {
|
||||
navigationRef.current.navigate(screenName);
|
||||
@@ -73,7 +85,7 @@ const WebSidebar = ({ navigationRef, currentRouteName }: WebSidebarProps) => {
|
||||
color: theme.colors.onPrimary, // Text color on primary background
|
||||
},
|
||||
inactiveLabel: {
|
||||
color: theme.colors.textSecondary, // Text color for inactive items
|
||||
color: theme.colors.onSurfaceVariant, // Text color for inactive items
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,6 +97,11 @@ const WebSidebar = ({ navigationRef, currentRouteName }: WebSidebarProps) => {
|
||||
</View>
|
||||
|
||||
{navItems.map((item) => {
|
||||
// Skip admin item if user is not admin
|
||||
if (item.adminOnly && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isActive = currentRouteName === item.name;
|
||||
return (
|
||||
<Pressable
|
||||
@@ -98,7 +115,7 @@ const WebSidebar = ({ navigationRef, currentRouteName }: WebSidebarProps) => {
|
||||
<MaterialCommunityIcons
|
||||
name={item.icon}
|
||||
size={24}
|
||||
color={isActive ? theme.colors.onPrimary : theme.colors.textSecondary}
|
||||
color={isActive ? theme.colors.onPrimary : theme.colors.onSurfaceVariant}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Text
|
||||
|
||||
@@ -5,9 +5,23 @@ import apiClient from '../api/client';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
import { getAccessToken, getRefreshToken, storeTokens, clearTokens } from '../api/client';
|
||||
|
||||
// Define UserRole enum matching the backend
|
||||
enum UserRole {
|
||||
USER = 'user',
|
||||
ADMIN = 'admin',
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
username: string;
|
||||
name: string;
|
||||
role: UserRole;
|
||||
// Add other user fields if needed
|
||||
}
|
||||
|
||||
interface AuthContextData {
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
user: UserData | null; // Add user data to context
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
@@ -15,6 +29,7 @@ interface AuthContextData {
|
||||
const AuthContext = createContext<AuthContextData>({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
user: null, // Initialize user as null
|
||||
login: async () => { throw new Error('AuthContext not initialized'); },
|
||||
logout: async () => { throw new Error('AuthContext not initialized'); },
|
||||
});
|
||||
@@ -26,15 +41,45 @@ interface AuthProviderProps {
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isAuthenticatedState, setIsAuthenticatedState] = useState<boolean>(false);
|
||||
const [userState, setUserState] = useState<UserData | null>(null); // State for user data
|
||||
|
||||
// Function to fetch user data
|
||||
const fetchUserData = useCallback(async () => {
|
||||
try {
|
||||
console.log("[AuthContext] fetchUserData: Fetching /user/me");
|
||||
const response = await apiClient.get<UserData>('/user/me');
|
||||
console.log("[AuthContext] fetchUserData: User data received:", response.data);
|
||||
setUserState(response.data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error("[AuthContext] fetchUserData: Error fetching user data:", error.response?.data || error.message);
|
||||
// If fetching user fails (e.g., token expired mid-session), log out
|
||||
await clearTokens();
|
||||
setIsAuthenticatedState(false);
|
||||
setUserState(null);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const checkAuthStatus = useCallback(async () => {
|
||||
const token = await getAccessToken();
|
||||
const hasToken = !!token;
|
||||
if (hasToken !== isAuthenticatedState) {
|
||||
setIsAuthenticatedState(hasToken);
|
||||
if (hasToken) {
|
||||
console.log("[AuthContext] checkAuthStatus: Token found, fetching user data.");
|
||||
const userData = await fetchUserData();
|
||||
if (userData) {
|
||||
setIsAuthenticatedState(true);
|
||||
} else {
|
||||
// Fetch failed, already handled logout in fetchUserData
|
||||
setIsAuthenticatedState(false);
|
||||
}
|
||||
} else {
|
||||
console.log("[AuthContext] checkAuthStatus: No token found.");
|
||||
setIsAuthenticatedState(false);
|
||||
setUserState(null);
|
||||
}
|
||||
return hasToken;
|
||||
}, [isAuthenticatedState]);
|
||||
return isAuthenticatedState; // Return the updated state
|
||||
}, [fetchUserData, isAuthenticatedState]); // Added isAuthenticatedState dependency
|
||||
|
||||
const loadInitialAuth = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
@@ -43,9 +88,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
await checkAuthStatus();
|
||||
console.log("[AuthContext] loadInitialAuth: Initial check complete.");
|
||||
} catch (error) {
|
||||
console.error("[AuthContext] loadInitialAuth: Error loading initial token:", error);
|
||||
console.error("[AuthContext] loadInitialAuth: Error during initial auth check:", error);
|
||||
await clearTokens();
|
||||
setIsAuthenticatedState(false);
|
||||
setUserState(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -58,7 +104,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
console.log("[AuthContext] login: Function called with:", username);
|
||||
try {
|
||||
console.log("[AuthContext] login: Preparing to call apiClient.post for /auth/login");
|
||||
// ... (existing login API call) ...
|
||||
const response = await apiClient.post(
|
||||
'/auth/login',
|
||||
'grant_type=password&username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password) + '&scope=&client_id=&client_secret=',
|
||||
@@ -70,10 +116,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[AuthContext] login: apiClient.post successful, response status:", response?.status);
|
||||
const { access_token, refresh_token } = response.data;
|
||||
console.log("[AuthContext] login: Response data received.");
|
||||
|
||||
// ... (existing token validation) ...
|
||||
if (!access_token || typeof access_token !== 'string' || !refresh_token) {
|
||||
console.error("[AuthContext] login: Invalid token structure received:", response.data);
|
||||
throw new Error('Invalid token received from server.');
|
||||
@@ -81,29 +125,30 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
|
||||
console.log('[AuthContext] login: Login successful, storing tokens.');
|
||||
await storeTokens(access_token, refresh_token);
|
||||
setIsAuthenticatedState(true);
|
||||
|
||||
// Fetch user data immediately after successful login
|
||||
const userData = await fetchUserData();
|
||||
if (userData) {
|
||||
setIsAuthenticatedState(true);
|
||||
} else {
|
||||
// Should not happen if login succeeded, but handle defensively
|
||||
throw new Error('Failed to fetch user data after login.');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
// ... (existing error handling) ...
|
||||
console.error("[AuthContext] login: Caught Error Object:", error);
|
||||
if (error.isAxiosError) {
|
||||
console.error("[AuthContext] login: Axios Error Details:");
|
||||
console.error(" Request Config:", error.config);
|
||||
console.error(" Response:", error.response?.status, error.response?.data);
|
||||
console.error(" Message:", error.message);
|
||||
}
|
||||
await clearTokens();
|
||||
setIsAuthenticatedState(false);
|
||||
setUserState(null);
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
}, [fetchUserData]); // Added fetchUserData dependency
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
console.log('[AuthContext] logout: Logging out.');
|
||||
const refreshToken = await getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
console.warn('[AuthContext] logout: No refresh token found to send to backend.');
|
||||
}
|
||||
|
||||
// ... (existing backend logout call) ...
|
||||
try {
|
||||
if (refreshToken) {
|
||||
console.log('[AuthContext] logout: Calling backend /auth/logout');
|
||||
@@ -115,6 +160,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
} finally {
|
||||
await clearTokens();
|
||||
setIsAuthenticatedState(false);
|
||||
setUserState(null); // Clear user data on logout
|
||||
console.log('[AuthContext] logout: Local tokens cleared and state updated.');
|
||||
}
|
||||
}, []);
|
||||
@@ -122,10 +168,12 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const contextValue = useMemo(() => ({
|
||||
isAuthenticated: isAuthenticatedState,
|
||||
isLoading,
|
||||
user: userState, // Provide user state
|
||||
login,
|
||||
logout,
|
||||
}), [isAuthenticatedState, isLoading, login, logout]);
|
||||
}), [isAuthenticatedState, isLoading, userState, login, logout]); // Added userState dependency
|
||||
|
||||
// ... (rest of the component: Provider, useAuth, AuthLoadingScreen) ...
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
{children}
|
||||
@@ -147,4 +195,7 @@ export const AuthLoadingScreen: React.FC = () => {
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: theme.colors.background }
|
||||
});
|
||||
return (<View style={styles.container}><ActivityIndicator size="large" color={theme.colors.primary} /></View>);
|
||||
}
|
||||
}
|
||||
|
||||
// Export UserRole if needed elsewhere
|
||||
export { UserRole };
|
||||
@@ -9,6 +9,8 @@ import ChatScreen from '../screens/ChatScreen';
|
||||
import CalendarScreen from '../screens/CalendarScreen';
|
||||
import ProfileScreen from '../screens/ProfileScreen';
|
||||
import EventFormScreen from '../screens/EventFormScreen';
|
||||
import AdminScreen from '../screens/AdminScreen'; // Import AdminScreen
|
||||
import { useAuth, UserRole } from '../contexts/AuthContext'; // Import useAuth and UserRole
|
||||
|
||||
import { MobileTabParamList } from '../types/navigation';
|
||||
|
||||
@@ -16,13 +18,15 @@ const Tab = createBottomTabNavigator<MobileTabParamList>();
|
||||
|
||||
const MobileTabNavigator = () => {
|
||||
const theme = useTheme();
|
||||
const { user } = useAuth(); // Get user data from context
|
||||
const isAdmin = user?.role === UserRole.ADMIN;
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName="DashboardTab"
|
||||
screenOptions={({ route }) => ({
|
||||
tabBarActiveTintColor: theme.colors.secondary,
|
||||
tabBarInactiveTintColor: theme.colors.textSecondary,
|
||||
tabBarInactiveTintColor: theme.colors.onSurfaceVariant, // Use onSurfaceVariant instead of textSecondary
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderTopColor: theme.colors.surface, // Or a subtle border
|
||||
@@ -30,7 +34,7 @@ const MobileTabNavigator = () => {
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
},
|
||||
headerTintColor: theme.colors.text,
|
||||
headerTintColor: theme.colors.onSurface, // Use onSurface instead of text
|
||||
tabBarIcon: ({ focused, color, size }) => {
|
||||
let iconName: string = 'help-circle'; // Default icon
|
||||
|
||||
@@ -42,6 +46,8 @@ const MobileTabNavigator = () => {
|
||||
iconName = focused ? 'calendar' : 'calendar-outline';
|
||||
} else if (route.name === 'ProfileTab') {
|
||||
iconName = focused ? 'account-circle' : 'account-circle-outline';
|
||||
} else if (route.name === 'AdminTab') { // Add icon for AdminTab
|
||||
iconName = focused ? 'shield-crown' : 'shield-crown-outline';
|
||||
}
|
||||
return <MaterialCommunityIcons name={iconName} size={size} color={color} />;
|
||||
},
|
||||
@@ -67,6 +73,14 @@ const MobileTabNavigator = () => {
|
||||
component={ProfileScreen}
|
||||
options={{ title: 'Profile', headerTitle: 'Profile' }}
|
||||
/>
|
||||
{/* Conditionally render Admin Tab */}
|
||||
{isAdmin && (
|
||||
<Tab.Screen
|
||||
name="AdminTab"
|
||||
component={AdminScreen}
|
||||
options={{ title: 'Admin', headerTitle: 'Admin Controls' }}
|
||||
/>
|
||||
)}
|
||||
</Tab.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ import ChatScreen from '../screens/ChatScreen';
|
||||
import CalendarScreen from '../screens/CalendarScreen';
|
||||
import ProfileScreen from '../screens/ProfileScreen';
|
||||
import EventFormScreen from '../screens/EventFormScreen';
|
||||
import AdminScreen from '../screens/AdminScreen'; // Import AdminScreen
|
||||
import { useAuth, UserRole } from '../contexts/AuthContext'; // Import useAuth and UserRole
|
||||
|
||||
import { WebContentStackParamList } from '../types/navigation';
|
||||
|
||||
@@ -15,6 +17,8 @@ const Stack = createNativeStackNavigator<WebContentStackParamList>();
|
||||
|
||||
const WebContentNavigator = () => {
|
||||
const theme = useTheme();
|
||||
const { user } = useAuth(); // Get user data
|
||||
const isAdmin = user?.role === UserRole.ADMIN;
|
||||
|
||||
return (
|
||||
<Stack.Navigator
|
||||
@@ -23,7 +27,7 @@ const WebContentNavigator = () => {
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
},
|
||||
headerTintColor: theme.colors.text,
|
||||
headerTintColor: theme.colors.onSurface, // Use onSurface instead of text
|
||||
headerTitleStyle: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
@@ -39,6 +43,10 @@ const WebContentNavigator = () => {
|
||||
<Stack.Screen name="Calendar" component={CalendarScreen} />
|
||||
<Stack.Screen name="Profile" component={ProfileScreen} />
|
||||
<Stack.Screen name="EventForm" component={EventFormScreen} />
|
||||
{/* Conditionally add Admin screen */}
|
||||
{isAdmin && (
|
||||
<Stack.Screen name="Admin" component={AdminScreen} options={{ title: 'Admin Controls' }}/>
|
||||
)}
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
81
interfaces/nativeapp/src/screens/AdminScreen.tsx
Normal file
81
interfaces/nativeapp/src/screens/AdminScreen.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Button, Checkbox, Text, ActivityIndicator, Snackbar } from 'react-native-paper';
|
||||
import { clearDatabase } from '../api/admin'; // Revert to standard import without extension
|
||||
|
||||
const AdminScreen = () => {
|
||||
const [isHardClear, setIsHardClear] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||
|
||||
const handleClearDb = async () => {
|
||||
setIsLoading(true);
|
||||
setSnackbarVisible(false);
|
||||
try {
|
||||
const response = await clearDatabase(isHardClear);
|
||||
setSnackbarMessage(response.message || 'Database cleared successfully.');
|
||||
setSnackbarVisible(true);
|
||||
} catch (error: any) {
|
||||
console.error("Error clearing database:", error);
|
||||
setSnackbarMessage(error.response?.data?.detail || 'Failed to clear database.');
|
||||
setSnackbarVisible(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text variant="headlineMedium" style={styles.title}>Admin Controls</Text>
|
||||
|
||||
<View style={styles.checkboxContainer}>
|
||||
<Checkbox
|
||||
status={isHardClear ? 'checked' : 'unchecked'}
|
||||
onPress={() => setIsHardClear(!isHardClear)}
|
||||
/>
|
||||
<Text onPress={() => setIsHardClear(!isHardClear)}>Hard Clear (Delete all data)</Text>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleClearDb}
|
||||
disabled={isLoading}
|
||||
style={styles.button}
|
||||
buttonColor="red" // Make it look dangerous
|
||||
>
|
||||
{isLoading ? <ActivityIndicator animating={true} color="white" /> : 'Clear Database'}
|
||||
</Button>
|
||||
|
||||
<Snackbar
|
||||
visible={snackbarVisible}
|
||||
onDismiss={() => setSnackbarVisible(false)}
|
||||
duration={Snackbar.DURATION_SHORT}
|
||||
>
|
||||
{snackbarMessage}
|
||||
</Snackbar>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
marginBottom: 30,
|
||||
},
|
||||
checkboxContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
button: {
|
||||
marginTop: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default AdminScreen;
|
||||
@@ -7,6 +7,7 @@ export type MobileTabParamList = {
|
||||
ChatTab: undefined;
|
||||
CalendarTab: undefined;
|
||||
ProfileTab: undefined;
|
||||
AdminTab?: undefined; // Add Admin tab (optional)
|
||||
};
|
||||
|
||||
// Screens within the Web Content Area Stack Navigator
|
||||
@@ -15,6 +16,7 @@ export type WebContentStackParamList = {
|
||||
Chat: undefined;
|
||||
Calendar: undefined;
|
||||
Profile: undefined;
|
||||
Admin?: undefined; // Add Admin screen
|
||||
EventForm?: { eventId?: number; selectedDate?: string };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user