[V1.0] Working application, added notifications.

Ready to upload to store.
This commit is contained in:
c-d-p
2025-04-27 00:39:52 +02:00
parent 04d9136b96
commit 62d6b8bdfd
86 changed files with 2250 additions and 240 deletions

View File

@@ -1,52 +1,99 @@
import React, { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Button, Checkbox, Text, ActivityIndicator, Snackbar } from 'react-native-paper';
import { Button, Checkbox, Text, ActivityIndicator, Snackbar, TextInput, Divider, useTheme } from 'react-native-paper'; // Added TextInput, Divider, useTheme
import { clearDatabase } from '../api/admin';
// Remove useNavigation import if no longer needed elsewhere in this file
// import { useNavigation } from '@react-navigation/native';
import { useAuth } from '../contexts/AuthContext'; // Import useAuth
import apiClient from '../api/client'; // Import apiClient
import { useAuth } from '../contexts/AuthContext';
const AdminScreen = () => {
const [isHardClear, setIsHardClear] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [snackbarVisible, setSnackbarVisible] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
// const navigation = useNavigation(); // Remove if not used elsewhere
const { logout } = useAuth(); // Get the logout function from context
const theme = useTheme(); // Get theme for styling if needed
// --- State for Clear DB ---
const [isHardClear, setIsHardClear] = useState(false);
const [isClearingDb, setIsClearingDb] = useState(false); // Renamed from isLoading
const [clearDbSnackbarVisible, setClearDbSnackbarVisible] = useState(false); // Renamed
const [clearDbSnackbarMessage, setClearDbSnackbarMessage] = useState(''); // Renamed
// --- State for Send Notification ---
const [username, setUsername] = useState('');
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [isSendingNotification, setIsSendingNotification] = useState(false); // New loading state
const [notificationError, setNotificationError] = useState<string | null>(null); // New error state
const [notificationSuccess, setNotificationSuccess] = useState<string | null>(null); // New success state
const { logout } = useAuth();
// --- Clear DB Handler ---
const handleClearDb = async () => {
setIsLoading(true);
setSnackbarVisible(false);
setIsClearingDb(true); // Use renamed state
setClearDbSnackbarVisible(false);
try {
const response = await clearDatabase(isHardClear);
setSnackbarMessage(response.message || 'Database cleared successfully.');
setSnackbarVisible(true);
setClearDbSnackbarMessage(response.message || 'Database cleared successfully.');
setClearDbSnackbarVisible(true);
// If hard clear was successful, trigger the logout process from AuthContext
if (isHardClear) {
console.log('Hard clear successful, calling logout...');
await logout(); // Call the logout function from AuthContext
// The RootNavigator will automatically switch to the AuthFlow
// No need to manually navigate or set loading to false here
return; // Exit early
await logout();
return;
}
} catch (error: any) {
console.error("Error clearing database:", error);
setSnackbarMessage(error.response?.data?.detail || 'Failed to clear database.');
setSnackbarVisible(true);
setClearDbSnackbarMessage(error.response?.data?.detail || 'Failed to clear database.');
setClearDbSnackbarVisible(true);
} finally {
// Only set loading to false if it wasn't a hard clear (as logout handles navigation)
if (!isHardClear) {
setIsLoading(false);
setIsClearingDb(false); // Use renamed state
}
}
};
// --- Send Notification Handler ---
const handleSendNotification = async () => {
if (!username || !title || !body) {
setNotificationError('Username, Title, and Body are required.');
setNotificationSuccess(null);
return;
}
setIsSendingNotification(true);
setNotificationError(null);
setNotificationSuccess(null);
try {
const response = await apiClient.post('/admin/send-notification', {
username,
title,
body,
// data: {} // Add optional data payload if needed
});
if (response.status === 200) {
setNotificationSuccess(response.data.message || 'Notification sent successfully!');
// Clear fields after success
setUsername('');
setTitle('');
setBody('');
} else {
setNotificationError(response.data?.detail || 'Failed to send notification.');
}
} catch (err: any) {
console.error("Error sending notification:", err.response?.data || err.message);
setNotificationError(err.response?.data?.detail || 'An error occurred while sending the notification.');
} finally {
setIsSendingNotification(false);
}
};
return (
<View style={styles.container}>
<Text variant="headlineMedium" style={styles.title}>Admin Controls</Text>
{/* --- Clear Database Section --- */}
<Text variant="titleMedium" style={styles.sectionTitle}>Clear Database</Text>
<View style={styles.checkboxContainer}>
<Checkbox
status={isHardClear ? 'checked' : 'unchecked'}
@@ -54,24 +101,68 @@ const AdminScreen = () => {
/>
<Text onPress={() => setIsHardClear(!isHardClear)}>Hard Clear (Delete all data)</Text>
</View>
<Button
mode="contained"
onPress={handleClearDb}
disabled={isLoading}
disabled={isClearingDb} // Use renamed state
style={styles.button}
buttonColor="red" // Make it look dangerous
buttonColor="red"
>
{isLoading ? <ActivityIndicator animating={true} color="white" /> : 'Clear Database'}
{isClearingDb ? <ActivityIndicator animating={true} color="white" /> : 'Clear Database'}
</Button>
<Snackbar
visible={snackbarVisible}
onDismiss={() => setSnackbarVisible(false)}
visible={clearDbSnackbarVisible} // Use renamed state
onDismiss={() => setClearDbSnackbarVisible(false)}
duration={Snackbar.DURATION_SHORT}
>
{snackbarMessage}
{clearDbSnackbarMessage} {/* Use renamed state */}
</Snackbar>
<Divider style={styles.divider} />
{/* --- Send Notification Section --- */}
<Text variant="titleMedium" style={styles.sectionTitle}>Send Push Notification</Text>
{notificationError && <Text style={[styles.message, { color: theme.colors.error }]}>{notificationError}</Text>}
{notificationSuccess && <Text style={[styles.message, { color: theme.colors.primary }]}>{notificationSuccess}</Text>}
<TextInput
label="Username"
value={username}
onChangeText={setUsername}
mode="outlined"
style={styles.input}
autoCapitalize="none"
disabled={isSendingNotification}
/>
<TextInput
label="Notification Title"
value={title}
onChangeText={setTitle}
mode="outlined"
style={styles.input}
disabled={isSendingNotification}
/>
<TextInput
label="Notification Body"
value={body}
onChangeText={setBody}
mode="outlined"
style={styles.input}
multiline
numberOfLines={3}
disabled={isSendingNotification}
/>
<Button
mode="contained"
onPress={handleSendNotification}
loading={isSendingNotification}
disabled={isSendingNotification}
style={styles.button}
>
{isSendingNotification ? 'Sending...' : 'Send Notification'}
</Button>
</View>
);
};
@@ -80,19 +171,37 @@ const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
alignItems: 'center',
// Removed justifyContent and alignItems to allow scrolling if content overflows
},
title: {
marginBottom: 30,
marginBottom: 20, // Reduced margin
textAlign: 'center',
},
sectionTitle: {
marginBottom: 15,
marginTop: 10, // Add some space before the title
textAlign: 'center',
},
checkboxContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
marginBottom: 10, // Reduced margin
justifyContent: 'center', // Center checkbox
},
button: {
marginTop: 10,
marginBottom: 10, // Add margin below button
},
input: {
marginBottom: 15,
},
message: {
marginBottom: 15,
textAlign: 'center',
fontWeight: 'bold',
},
divider: {
marginVertical: 30, // Add vertical space around the divider
},
});

View File

@@ -143,13 +143,6 @@ const EventFormScreen = () => {
const handleStartDateConfirm = (date: Date) => {
setStartDate(date);
setWebStartDateInput(formatForWebInput(date)); // Update web input state
// Optional: Auto-set end date if it's before start date or null
if (!endDate || endDate < date) {
const newEndDate = new Date(date);
newEndDate.setHours(date.getHours() + 1); // Default to 1 hour later
setEndDate(newEndDate);
setWebEndDateInput(formatForWebInput(newEndDate)); // Update web input state
}
validateForm({ start: date }); // Validate after setting
hideStartDatePicker();
};
@@ -189,13 +182,6 @@ const EventFormScreen = () => {
if (isValid(parsedDate) && text.length >= 15) { // Basic length check for 'yyyy-MM-dd HH:mm'
if (type === 'start') {
setStartDate(parsedDate);
// Optional: Auto-set end date
if (!endDate || endDate < parsedDate) {
const newEndDate = new Date(parsedDate);
newEndDate.setHours(parsedDate.getHours() + 1);
setEndDate(newEndDate);
setWebEndDateInput(formatForWebInput(newEndDate)); // Update other web input too
}
validateForm({ start: parsedDate }); // Validate with the actual Date
} else {
setEndDate(parsedDate);