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
d18f9d32b0
commit
f7eaf0adcf
24 files changed
+1826
-393
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, Image, ActivityIndicator, Alert, Modal, Dimensions } from "react-native";
|
||||
import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, ActivityIndicator, Alert, Modal, Dimensions } from "react-native";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
@@ -15,6 +16,7 @@ import { Database } from "../../../src/types/supabase";
|
||||
import { GradientBackground } from "../../../src/components/ui";
|
||||
import { colors, gradients, spacing, radius, typography } from "../../../src/theme";
|
||||
import { Events } from "../../../src/lib/analytics";
|
||||
import { getCachedSignedUrl, getStoragePath } from "../../../src/lib/imageCache";
|
||||
|
||||
type Message = Database["public"]["Tables"]["messages"]["Row"];
|
||||
|
||||
@@ -356,6 +358,8 @@ export default function ChatScreen() {
|
||||
<Image
|
||||
source={{ uri: partner.avatar_url }}
|
||||
style={styles.headerAvatar}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : (
|
||||
<LinearGradient
|
||||
@@ -492,7 +496,8 @@ export default function ChatScreen() {
|
||||
<Image
|
||||
source={{ uri: fullScreenImage }}
|
||||
style={styles.fullScreenImage}
|
||||
resizeMode="contain"
|
||||
contentFit="contain"
|
||||
cachePolicy="disk"
|
||||
onLoadStart={() => setFullScreenImageLoading(true)}
|
||||
onLoadEnd={() => setFullScreenImageLoading(false)}
|
||||
/>
|
||||
@@ -503,16 +508,6 @@ export default function ChatScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
// Extract storage path from media_path (handles both old full URLs and new path-only format)
|
||||
function getStoragePath(mediaPath: string): string {
|
||||
if (mediaPath.startsWith('http')) {
|
||||
// Old format: extract path from full URL
|
||||
const match = mediaPath.match(/\/chat-media\/(.+)$/);
|
||||
return match ? match[1] : mediaPath;
|
||||
}
|
||||
return mediaPath;
|
||||
}
|
||||
|
||||
function MessageContent({
|
||||
item,
|
||||
isMe,
|
||||
@@ -530,26 +525,26 @@ function MessageContent({
|
||||
useEffect(() => {
|
||||
if (!item.media_path) return;
|
||||
|
||||
let isMounted = true;
|
||||
|
||||
const fetchSignedUrl = async () => {
|
||||
const storagePath = getStoragePath(item.media_path!);
|
||||
const { data, error } = await supabase.storage
|
||||
.from('chat-media')
|
||||
.createSignedUrl(storagePath, 3600); // 1 hour expiry
|
||||
const url = await getCachedSignedUrl(storagePath);
|
||||
|
||||
if (error) {
|
||||
console.error('Failed to get signed URL:', error);
|
||||
setUrlError(true);
|
||||
return;
|
||||
}
|
||||
if (!isMounted) return;
|
||||
|
||||
if (data?.signedUrl) {
|
||||
setSignedUrl(data.signedUrl);
|
||||
if (url) {
|
||||
setSignedUrl(url);
|
||||
} else {
|
||||
setUrlError(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSignedUrl();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [item.media_path]);
|
||||
|
||||
if (item.media_path) {
|
||||
@@ -568,6 +563,8 @@ function MessageContent({
|
||||
source={{ uri: signedUrl }}
|
||||
style={styles.messageImage}
|
||||
blurRadius={(!isMe && !isViewed) ? 20 : 0}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : urlError ? (
|
||||
<View style={[styles.messageImage, { justifyContent: 'center', alignItems: 'center', backgroundColor: colors.surface }]}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Platform, useWindowDimensions, TextInput, Modal, ActivityIndicator, Linking, Switch, Image, ActionSheetIOS } from "react-native";
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Platform, useWindowDimensions, TextInput, Modal, ActivityIndicator, Linking, Switch, ActionSheetIOS } from "react-native";
|
||||
import { Image } from "expo-image";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { useAuthStore, useMatchStore, useMessageStore, usePacksStore, useSubscriptionStore } from "../../src/store";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -38,6 +39,7 @@ export default function ProfileScreen() {
|
||||
const [deleteConfirmText, setDeleteConfirmText] = useState("");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
|
||||
const [isResettingProgress, setIsResettingProgress] = useState(false);
|
||||
const [showPaywall, setShowPaywall] = useState(false);
|
||||
const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true);
|
||||
const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false);
|
||||
@@ -381,6 +383,40 @@ export default function ProfileScreen() {
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetProgress = async () => {
|
||||
Alert.alert(
|
||||
"Reset Progress",
|
||||
"This will delete all your swipes, matches, and chat messages. You'll stay connected with your partner and can start fresh. This cannot be undone.",
|
||||
[
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Reset",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
setIsResettingProgress(true);
|
||||
try {
|
||||
const { error } = await supabase.functions.invoke("reset-couple-progress", {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Clear local stores
|
||||
clearMatches();
|
||||
clearMessages();
|
||||
|
||||
Alert.alert("Success", "Your progress has been reset. You can now start swiping again!");
|
||||
} catch (error) {
|
||||
Alert.alert("Error", "Failed to reset progress. Please try again.");
|
||||
} finally {
|
||||
setIsResettingProgress(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSignOut = () => {
|
||||
Alert.alert(
|
||||
"Sign Out",
|
||||
@@ -437,6 +473,8 @@ export default function ProfileScreen() {
|
||||
<Image
|
||||
source={{ uri: user.avatar_url }}
|
||||
style={styles.avatarImage}
|
||||
cachePolicy="disk"
|
||||
transition={200}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.avatarInner}>
|
||||
@@ -793,10 +831,50 @@ export default function ProfileScreen() {
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* Reset Progress Section - Only show if in a couple */}
|
||||
{couple && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(500).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.resetSectionTitle}>Reset Progress</Text>
|
||||
<GlassCard style={styles.resetCard}>
|
||||
<View style={styles.resetContent}>
|
||||
<View style={styles.resetInfo}>
|
||||
<View style={styles.resetIconContainer}>
|
||||
<Ionicons name="refresh" size={20} color={colors.warning} />
|
||||
</View>
|
||||
<View style={styles.resetTextContainer}>
|
||||
<Text style={styles.resetTitle}>Start Fresh</Text>
|
||||
<Text style={styles.resetDescription}>
|
||||
Delete all swipes, matches, and chats while keeping your partner connection.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.resetButton, isResettingProgress && styles.resetButtonDisabled]}
|
||||
onPress={handleResetProgress}
|
||||
disabled={isResettingProgress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{isResettingProgress ? (
|
||||
<ActivityIndicator size="small" color={colors.warning} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="refresh-outline" size={16} color={colors.warning} />
|
||||
<Text style={styles.resetButtonText}>Reset</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Debug Section - Only show in development builds */}
|
||||
{__DEV__ && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(500).duration(500)}
|
||||
entering={FadeInDown.delay(couple ? 550 : 500).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.debugSectionTitle}>Debug</Text>
|
||||
@@ -883,7 +961,7 @@ export default function ProfileScreen() {
|
||||
{/* Danger Zone - Only show if in a couple */}
|
||||
{couple && (
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(525).duration(500)}
|
||||
entering={FadeInDown.delay(600).duration(500)}
|
||||
style={styles.section}
|
||||
>
|
||||
<Text style={styles.dangerSectionTitle}>Danger Zone</Text>
|
||||
@@ -915,7 +993,7 @@ export default function ProfileScreen() {
|
||||
|
||||
{/* Version */}
|
||||
<Animated.View
|
||||
entering={FadeInDown.delay(couple ? 575 : 525).duration(500)}
|
||||
entering={FadeInDown.delay(couple ? 650 : 525).duration(500)}
|
||||
style={styles.versionContainer}
|
||||
>
|
||||
<View style={styles.versionBadge}>
|
||||
@@ -1349,6 +1427,69 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
// Reset Progress styles
|
||||
resetSectionTitle: {
|
||||
...typography.caption1,
|
||||
color: colors.warning,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1.5,
|
||||
marginBottom: spacing.sm,
|
||||
marginLeft: spacing.xs,
|
||||
},
|
||||
resetCard: {
|
||||
borderColor: 'rgba(243, 156, 18, 0.3)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
resetContent: {
|
||||
gap: spacing.md,
|
||||
},
|
||||
resetInfo: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
resetIconContainer: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
backgroundColor: colors.warningLight,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
resetTextContainer: {
|
||||
flex: 1,
|
||||
marginLeft: spacing.md,
|
||||
},
|
||||
resetTitle: {
|
||||
...typography.headline,
|
||||
color: colors.text,
|
||||
marginBottom: spacing.xs,
|
||||
},
|
||||
resetDescription: {
|
||||
...typography.subhead,
|
||||
color: colors.textSecondary,
|
||||
lineHeight: 20,
|
||||
},
|
||||
resetButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.warningLight,
|
||||
paddingVertical: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderRadius: radius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(243, 156, 18, 0.3)',
|
||||
gap: spacing.xs,
|
||||
alignSelf: "flex-end",
|
||||
},
|
||||
resetButtonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
resetButtonText: {
|
||||
...typography.subhead,
|
||||
color: colors.warning,
|
||||
fontWeight: "600",
|
||||
},
|
||||
// Danger Zone styles
|
||||
dangerSectionTitle: {
|
||||
...typography.caption1,
|
||||
|
||||
Reference in new issue
Block a user