313 lines
8.8 KiB
TypeScript
313 lines
8.8 KiB
TypeScript
// app/add_review.tsx
|
|
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TextInput,
|
|
Button,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Alert,
|
|
TouchableOpacity,
|
|
} from 'react-native';
|
|
import { Picker } from '@react-native-picker/picker'; // If you were using a picker
|
|
import { useRouter, useLocalSearchParams } from 'expo-router'; // Import useRouter and useLocalSearchParams
|
|
|
|
// Ensure interfaces match your PrayerSpacesListPage.tsx and backend expectations
|
|
interface ReviewPayload {
|
|
user: string;
|
|
place: string; // Backend expects place NAME string for lookup
|
|
quiet: number | null;
|
|
clean: number | null;
|
|
private: number | null;
|
|
cleanWudu: number | null;
|
|
childFriendly: number | null;
|
|
safe: number | null;
|
|
notes?: string; // Corresponds to comment in your frontend if added
|
|
}
|
|
|
|
interface AddReviewResponse {
|
|
message: string;
|
|
reviewId?: string;
|
|
}
|
|
|
|
const BACKEND_URL = 'http://132.145.65.145:8080'; // Your backend URL
|
|
const HARDCODED_USERNAME = 'testuser'; // Hardcoded username string, matching your seed user
|
|
|
|
// --- Emoji Rating Helpers (copied from PrayerSpacesListPage.tsx) ---
|
|
// This ensures the new page has these functions available
|
|
function averageRatingToEmoji(avg: number) {
|
|
if (avg < 1.66) {
|
|
return "😞";
|
|
} else if (avg < 2.33) {
|
|
return "😐";
|
|
} else {
|
|
return "😊";
|
|
}
|
|
}
|
|
|
|
interface EmojiRatingSelectorProps {
|
|
label: string;
|
|
value: number | null;
|
|
onSelect: (value: number) => void;
|
|
style?: object;
|
|
}
|
|
|
|
const EmojiRatingSelector: React.FC<EmojiRatingSelectorProps> = ({ label, value, onSelect, style }) => {
|
|
const emojis = [
|
|
{ emoji: "😞", value: 1, label: "Poor" },
|
|
{ emoji: "😐", value: 2, label: "Okay" },
|
|
{ emoji: "😊", value: 3, label: "Great" }
|
|
];
|
|
|
|
return (
|
|
<View style={[addReviewStyles.ratingContainer, style]}>
|
|
<Text style={addReviewStyles.ratingLabel}>{label}</Text>
|
|
<View style={addReviewStyles.emojiRow}>
|
|
{emojis.map((item) => (
|
|
<TouchableOpacity
|
|
key={item.value}
|
|
style={[
|
|
addReviewStyles.emojiButton,
|
|
value === item.value && addReviewStyles.selectedEmoji
|
|
]}
|
|
onPress={() => onSelect(item.value)}
|
|
>
|
|
<Text style={addReviewStyles.emojiText}>{item.emoji}</Text>
|
|
<Text style={addReviewStyles.emojiLabel}>{item.label}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
// --- End Emoji Rating Helpers ---
|
|
|
|
|
|
export default function AddReviewScreen() {
|
|
const router = useRouter();
|
|
const params = useLocalSearchParams(); // Get params passed from navigation
|
|
const placeName = typeof params.placeName === 'string' ? params.placeName : 'Unknown Place';
|
|
const placeId = typeof params.placeId === 'string' ? params.placeId : ''; // Place ID for potential future use or debugging
|
|
|
|
const [quiet, setQuiet] = useState<number | null>(null);
|
|
const [clean, setClean] = useState<number | null>(null);
|
|
const [privacy, setPrivacy] = useState<number | null>(null);
|
|
const [cleanWudu, setCleanWudu] = useState<number | null>(null);
|
|
const [childFriendly, setChildFriendly] = useState<number | null>(null);
|
|
const [safe, setSafe] = useState<number | null>(null);
|
|
const [notes, setNotes] = useState(''); // Changed from comment to notes to match backend model name
|
|
|
|
const isValidRating = (val: number | null): boolean => {
|
|
return val !== null && val >= 1 && val <= 3;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
// Validate all rating fields
|
|
if (!quiet || !clean || !privacy || !cleanWudu || !childFriendly || !safe) {
|
|
Alert.alert('Missing Info', 'Please fill in all rating fields.');
|
|
return;
|
|
}
|
|
|
|
if (![quiet, clean, privacy, cleanWudu, childFriendly, safe].every(isValidRating)) {
|
|
Alert.alert('Invalid Input', 'Please select a rating for each category (1-3).');
|
|
return;
|
|
}
|
|
|
|
// Prepare payload for backend - sending place NAME as string
|
|
const payload: ReviewPayload = {
|
|
user: HARDCODED_USERNAME,
|
|
place: placeName, // IMPORTANT: Sending place NAME, not ID, as per your backend's SaveReview
|
|
quiet: quiet,
|
|
clean: clean,
|
|
private: privacy,
|
|
cleanWudu: cleanWudu,
|
|
childFriendly: childFriendly,
|
|
safe: safe,
|
|
notes: notes, // Sending notes field
|
|
};
|
|
|
|
console.log("Sending review payload from AddReviewScreen:", payload);
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/reviews/new`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
console.error("Backend error response:", errorData);
|
|
throw new Error(errorData.message || `HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const data: AddReviewResponse = await response.json();
|
|
Alert.alert('Success', data.message || 'Review submitted!');
|
|
|
|
// Clear form and navigate back
|
|
setQuiet(null);
|
|
setClean(null);
|
|
setPrivacy(null);
|
|
setCleanWudu(null);
|
|
setChildFriendly(null);
|
|
setSafe(null);
|
|
setNotes(''); // Clear notes field
|
|
|
|
router.back(); // Go back to the previous screen (PrayerSpacesListPage)
|
|
} catch (error: any) {
|
|
Alert.alert('Error', `Could not send review: ${error.message || error}`);
|
|
console.error("Review submission error:", error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ScrollView style={addReviewStyles.container} contentContainerStyle={addReviewStyles.contentContainer}>
|
|
<Text style={addReviewStyles.title}>Submit a Review</Text>
|
|
<Text style={addReviewStyles.subtitle}>For: {placeName}</Text>
|
|
|
|
{/* Emoji Rating Selectors */}
|
|
<EmojiRatingSelector
|
|
label="Cleanliness Rating"
|
|
value={clean}
|
|
onSelect={setClean}
|
|
/>
|
|
<EmojiRatingSelector
|
|
label="Wudu Cleanliness Rating"
|
|
value={cleanWudu}
|
|
onSelect={setCleanWudu}
|
|
/>
|
|
<EmojiRatingSelector
|
|
label="Quietness Rating"
|
|
value={quiet}
|
|
onSelect={setQuiet}
|
|
/>
|
|
<EmojiRatingSelector
|
|
label="Privacy Rating"
|
|
value={privacy}
|
|
onSelect={setPrivacy}
|
|
/>
|
|
<EmojiRatingSelector
|
|
label="Child Friendliness Rating"
|
|
value={childFriendly}
|
|
onSelect={setChildFriendly}
|
|
/>
|
|
<EmojiRatingSelector
|
|
label="Safety Rating"
|
|
value={safe}
|
|
onSelect={setSafe}
|
|
/>
|
|
|
|
<Text style={addReviewStyles.label}>Your Comments (optional)</Text>
|
|
<TextInput
|
|
style={[addReviewStyles.input, addReviewStyles.notesInput]}
|
|
placeholder="Add your thoughts about this place..."
|
|
value={notes}
|
|
onChangeText={setNotes}
|
|
multiline
|
|
numberOfLines={4}
|
|
textAlignVertical="top"
|
|
/>
|
|
|
|
<Button title="Submit Review" onPress={handleSubmit} />
|
|
<TouchableOpacity onPress={() => router.back()} style={addReviewStyles.cancelButton}>
|
|
<Text style={addReviewStyles.cancelButtonText}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const addReviewStyles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f8f9fa',
|
|
},
|
|
contentContainer: {
|
|
padding: 20,
|
|
paddingBottom: 40,
|
|
},
|
|
title: {
|
|
fontSize: 26,
|
|
fontWeight: 'bold',
|
|
marginBottom: 10,
|
|
textAlign: 'center',
|
|
color: '#333',
|
|
},
|
|
subtitle: {
|
|
fontSize: 18,
|
|
marginBottom: 25,
|
|
textAlign: 'center',
|
|
color: '#555',
|
|
fontWeight: '600',
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
marginBottom: 8,
|
|
color: '#333',
|
|
marginTop: 10, // Add some top margin for spacing
|
|
},
|
|
input: {
|
|
backgroundColor: '#fff',
|
|
borderWidth: 1,
|
|
borderColor: '#ddd',
|
|
borderRadius: 8,
|
|
paddingHorizontal: 15,
|
|
paddingVertical: 12,
|
|
fontSize: 16,
|
|
marginBottom: 15,
|
|
},
|
|
notesInput: {
|
|
height: 100,
|
|
paddingTop: 12,
|
|
},
|
|
// Styles for EmojiRatingSelector (copied from PrayerSpacesListPage)
|
|
ratingContainer: {
|
|
marginBottom: 15,
|
|
alignItems: 'center',
|
|
backgroundColor: '#fff', // Added background for better visibility
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: '#ddd',
|
|
paddingVertical: 12,
|
|
},
|
|
ratingLabel: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
marginBottom: 8,
|
|
color: '#333',
|
|
},
|
|
emojiRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
width: '90%',
|
|
},
|
|
emojiButton: {
|
|
padding: 8, // Slightly less padding for touch area
|
|
borderRadius: 5,
|
|
alignItems: 'center',
|
|
},
|
|
selectedEmoji: {
|
|
backgroundColor: '#e3f2fd',
|
|
borderColor: '#007AFF',
|
|
borderWidth: 1,
|
|
},
|
|
emojiText: {
|
|
fontSize: 30,
|
|
},
|
|
emojiLabel: {
|
|
fontSize: 12,
|
|
color: '#666',
|
|
marginTop: 5,
|
|
},
|
|
cancelButton: {
|
|
marginTop: 15,
|
|
paddingVertical: 10,
|
|
alignItems: 'center',
|
|
},
|
|
cancelButtonText: {
|
|
color: '#dc3545',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
}); |