mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Update
This commit is contained in:
1 parent
456459bb00
commit
5b97bc6f56
60 files changed
+1440
-1974
No files matched your search
@@ -8,6 +8,7 @@ import { useAuthStore, useMatchStore, useMessageStore, useSubscriptionStore, use
|
||||
import { colors, gradients, blur, radius, spacing, typography, shadows } from "../../src/theme";
|
||||
import { supabase } from "../../src/lib/supabase";
|
||||
import { isBiometricEnabled } from "../../src/lib/biometricAuth";
|
||||
import { checkAndRegisterPushToken } from "../../src/lib/notifications";
|
||||
import { BiometricLockScreen } from "../../src/components/BiometricLockScreen";
|
||||
import type { MatchWithQuestion } from "../../src/types";
|
||||
import type { Database } from "../../src/types/supabase";
|
||||
@@ -182,6 +183,14 @@ export default function AppLayout() {
|
||||
}
|
||||
}, [user?.id, initializeRevenueCat]);
|
||||
|
||||
// Check and register push token for this device
|
||||
// This handles the case where user onboarded on a different device
|
||||
useEffect(() => {
|
||||
if (user?.id && user?.onboarding_completed) {
|
||||
checkAndRegisterPushToken(user.id);
|
||||
}
|
||||
}, [user?.id, user?.onboarding_completed]);
|
||||
|
||||
// Subscribe to realtime match notifications
|
||||
useEffect(() => {
|
||||
if (!user?.couple_id) return;
|
||||
|
||||
@@ -211,7 +211,25 @@ export default function ChatScreen() {
|
||||
const handlePickImage = async () => {
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
allowsEditing: true,
|
||||
quality: 0.7,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
uploadImage(result.assets[0].uri);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTakePhoto = async () => {
|
||||
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
Alert.alert(
|
||||
"Camera Permission",
|
||||
"Please allow camera access to take photos."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
quality: 0.7,
|
||||
});
|
||||
|
||||
@@ -423,6 +441,7 @@ export default function ChatScreen() {
|
||||
onChangeText={handleTyping}
|
||||
onSend={handleSend}
|
||||
onPickImage={handlePickImage}
|
||||
onTakePhoto={handleTakePhoto}
|
||||
/>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
@@ -611,23 +630,29 @@ function InputBar({
|
||||
onChangeText,
|
||||
onSend,
|
||||
onPickImage,
|
||||
onTakePhoto,
|
||||
}: {
|
||||
inputText: string;
|
||||
uploading: boolean;
|
||||
onChangeText: (text: string) => void;
|
||||
onSend: () => void;
|
||||
onPickImage: () => void;
|
||||
onTakePhoto: () => void;
|
||||
}) {
|
||||
return (
|
||||
<View style={styles.inputContainer}>
|
||||
<TouchableOpacity onPress={onPickImage} disabled={uploading} style={styles.attachButton}>
|
||||
<TouchableOpacity onPress={onTakePhoto} disabled={uploading} style={styles.attachButton}>
|
||||
{uploading ? (
|
||||
<ActivityIndicator color={colors.primary} size="small" />
|
||||
) : (
|
||||
<Ionicons name="image-outline" size={22} color={colors.textSecondary} />
|
||||
<Ionicons name="camera-outline" size={22} color={colors.textSecondary} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity onPress={onPickImage} disabled={uploading} style={styles.attachButton}>
|
||||
<Ionicons name="image-outline" size={22} color={colors.textSecondary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.inputFieldWrapper}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
|
||||
@@ -123,10 +123,13 @@ export default function PacksScreen() {
|
||||
</View>
|
||||
<View style={styles.packInfo}>
|
||||
<View style={styles.packHeader}>
|
||||
<Text style={[
|
||||
styles.packName,
|
||||
isPremiumLocked && styles.packNameLocked
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
styles.packName,
|
||||
isPremiumLocked && styles.packNameLocked
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
{item.is_premium && (
|
||||
@@ -359,7 +362,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
packInfo: {
|
||||
flex: 1,
|
||||
marginRight: spacing.sm,
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
packHeader: {
|
||||
flexDirection: "row",
|
||||
@@ -370,6 +373,8 @@ const styles = StyleSheet.create({
|
||||
...typography.headline,
|
||||
color: colors.text,
|
||||
marginRight: spacing.sm,
|
||||
flex: 1,
|
||||
flexShrink: 1,
|
||||
},
|
||||
packNameLocked: {
|
||||
color: colors.textSecondary,
|
||||
|
||||
+127
-12
@@ -1,4 +1,4 @@
|
||||
import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator } from "react-native";
|
||||
import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, Modal, Pressable } from "react-native";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { supabase } from "../../src/lib/supabase";
|
||||
@@ -6,12 +6,26 @@ import { usePacksStore } from "../../src/store";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Question } from "../../src/types";
|
||||
|
||||
type TooltipType = 'initiator' | 'coupleType' | null;
|
||||
|
||||
const TOOLTIP_CONTENT = {
|
||||
initiator: {
|
||||
title: 'Initiator Filter',
|
||||
description: 'The "initiator" is the first person in the couple to see and answer this question. This icon shows which genders can be the initiator. Their partner will always see the question afterward, regardless of this setting.',
|
||||
},
|
||||
coupleType: {
|
||||
title: 'Couple Type Filter',
|
||||
description: 'This shows which couple compositions can see this question. For example, a male+female icon means the question is only shown to couples where one partner is male and one is female.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function PackDetailsScreen() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const router = useRouter();
|
||||
const { packs, fetchPacks } = usePacksStore();
|
||||
const [questions, setQuestions] = useState<Question[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [activeTooltip, setActiveTooltip] = useState<TooltipType>(null);
|
||||
|
||||
const pack = packs.find((p) => p.id === id);
|
||||
|
||||
@@ -57,8 +71,33 @@ export default function PackDetailsScreen() {
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
{item.allowed_couple_genders && item.allowed_couple_genders.length > 0 ? (
|
||||
{/* Initiator indicator */}
|
||||
{item.target_user_genders && item.target_user_genders.length > 0 ? (
|
||||
<View style={styles.targetsContainer}>
|
||||
<Pressable
|
||||
style={[styles.targetBadge, styles.initiatorBadge]}
|
||||
onPress={() => setActiveTooltip('initiator')}
|
||||
>
|
||||
<Ionicons name="arrow-forward" size={12} color="#f39c12" style={{ marginRight: 4 }} />
|
||||
{item.target_user_genders.map((g, i) => (
|
||||
<Ionicons
|
||||
key={g}
|
||||
name={g === 'male' ? 'male' : g === 'female' ? 'female' : 'person'}
|
||||
size={14}
|
||||
color="#f39c12"
|
||||
style={i > 0 ? { marginLeft: 4 } : {}}
|
||||
/>
|
||||
))}
|
||||
<Ionicons name="help-circle-outline" size={14} color="#f39c12" style={{ marginLeft: 6 }} />
|
||||
</Pressable>
|
||||
</View>
|
||||
) : null}
|
||||
{/* Couple type indicator */}
|
||||
{item.allowed_couple_genders && item.allowed_couple_genders.length > 0 ? (
|
||||
<Pressable
|
||||
style={styles.targetsContainer}
|
||||
onPress={() => setActiveTooltip('coupleType')}
|
||||
>
|
||||
{item.allowed_couple_genders.map((g) => {
|
||||
const icons = [];
|
||||
if (g === 'male+male') {
|
||||
@@ -75,21 +114,26 @@ export default function PackDetailsScreen() {
|
||||
<Ionicons
|
||||
key={i}
|
||||
name={icon as any}
|
||||
size={10}
|
||||
size={14}
|
||||
color="#ccc"
|
||||
style={i > 0 ? { marginLeft: 2 } : {}}
|
||||
style={i > 0 ? { marginLeft: 4 } : {}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Ionicons name="help-circle-outline" size={14} color="#ccc" style={{ marginLeft: 6 }} />
|
||||
</Pressable>
|
||||
) : (
|
||||
<View style={styles.targetsContainer}>
|
||||
<Pressable
|
||||
style={styles.targetsContainer}
|
||||
onPress={() => setActiveTooltip('coupleType')}
|
||||
>
|
||||
<View style={styles.targetBadge}>
|
||||
<Ionicons name="people" size={10} color="#ccc" />
|
||||
<Ionicons name="people" size={14} color="#ccc" />
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="help-circle-outline" size={14} color="#ccc" style={{ marginLeft: 6 }} />
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -132,6 +176,34 @@ export default function PackDetailsScreen() {
|
||||
keyExtractor={(item) => item.id}
|
||||
contentContainerStyle={styles.list}
|
||||
/>
|
||||
|
||||
{/* Tooltip Modal */}
|
||||
<Modal
|
||||
visible={activeTooltip !== null}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setActiveTooltip(null)}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.modalOverlay}
|
||||
onPress={() => setActiveTooltip(null)}
|
||||
>
|
||||
<View style={styles.tooltipContainer}>
|
||||
<Text style={styles.tooltipTitle}>
|
||||
{activeTooltip && TOOLTIP_CONTENT[activeTooltip].title}
|
||||
</Text>
|
||||
<Text style={styles.tooltipDescription}>
|
||||
{activeTooltip && TOOLTIP_CONTENT[activeTooltip].description}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.tooltipButton}
|
||||
onPress={() => setActiveTooltip(null)}
|
||||
>
|
||||
<Text style={styles.tooltipButtonText}>Got it</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -233,16 +305,59 @@ const styles = StyleSheet.create({
|
||||
targetsContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
targetBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
},
|
||||
initiatorBadge: {
|
||||
backgroundColor: 'rgba(243, 156, 18, 0.15)',
|
||||
borderColor: 'rgba(243, 156, 18, 0.3)',
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 24,
|
||||
},
|
||||
tooltipContainer: {
|
||||
backgroundColor: '#16213e',
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
maxWidth: 320,
|
||||
borderWidth: 1,
|
||||
borderColor: '#0f3460',
|
||||
},
|
||||
tooltipTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#fff',
|
||||
marginBottom: 12,
|
||||
},
|
||||
tooltipDescription: {
|
||||
fontSize: 14,
|
||||
color: '#ccc',
|
||||
lineHeight: 22,
|
||||
marginBottom: 20,
|
||||
},
|
||||
tooltipButton: {
|
||||
backgroundColor: '#e94560',
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
tooltipButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user