mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Add lifecycle push notifications and refine UI
Send push notifications when partner disconnects, resets progress, or deletes relationship. Update Matches screen with new styling and remove legacy premium elements. Remove message content preview from admin User Activity page.
This commit is contained in:
1 parent
a567fba349
commit
994dd2a295
5 files changed
+174
-123
No files matched your search
@@ -30,8 +30,6 @@ import {
|
|||||||
Heart,
|
Heart,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
Image as ImageIcon,
|
|
||||||
Video as VideoIcon,
|
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
@@ -546,7 +544,6 @@ export function UserActivityPage() {
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>User</TableHead>
|
<TableHead>User</TableHead>
|
||||||
<TableHead>Message</TableHead>
|
|
||||||
<TableHead>Context</TableHead>
|
<TableHead>Context</TableHead>
|
||||||
<TableHead>Time</TableHead>
|
<TableHead>Time</TableHead>
|
||||||
<TableHead>Actions</TableHead>
|
<TableHead>Actions</TableHead>
|
||||||
@@ -555,13 +552,13 @@ export function UserActivityPage() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{loading.messages ? (
|
{loading.messages ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5} className="text-center py-8">
|
<TableCell colSpan={4} className="text-center py-8">
|
||||||
<div className="flex justify-center"><Skeleton className="h-6 w-32" /></div>
|
<div className="flex justify-center"><Skeleton className="h-6 w-32" /></div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : messages.length === 0 ? (
|
) : messages.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5} className="text-center py-8 text-muted-foreground">
|
<TableCell colSpan={4} className="text-center py-8 text-muted-foreground">
|
||||||
No messages yet.
|
No messages yet.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -569,21 +566,6 @@ export function UserActivityPage() {
|
|||||||
messages.map((item) => (
|
messages.map((item) => (
|
||||||
<TableRow key={item.id}>
|
<TableRow key={item.id}>
|
||||||
<TableCell>{renderUserLink(item.profile)}</TableCell>
|
<TableCell>{renderUserLink(item.profile)}</TableCell>
|
||||||
<TableCell className="max-w-[300px]">
|
|
||||||
{item.media_path ? (
|
|
||||||
<span className="inline-flex items-center gap-1 text-muted-foreground">
|
|
||||||
{item.media_type === 'video' ? (
|
|
||||||
<><VideoIcon className="h-4 w-4" /> [Video]</>
|
|
||||||
) : (
|
|
||||||
<><ImageIcon className="h-4 w-4" /> [Image]</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="truncate block">
|
|
||||||
{item.content || <span className="text-muted-foreground italic">Empty message</span>}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="max-w-[200px]">
|
<TableCell className="max-w-[200px]">
|
||||||
<span className="truncate block text-muted-foreground text-sm">
|
<span className="truncate block text-muted-foreground text-sm">
|
||||||
{item.match?.question?.text || 'Unknown match'}
|
{item.match?.question?.text || 'Unknown match'}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import Animated, {
|
|||||||
FadeInRight,
|
FadeInRight,
|
||||||
FadeInUp,
|
FadeInUp,
|
||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import { GradientBackground, GlassCard, GlassButton, DecorativeSeparator } from "../../src/components/ui";
|
import { GradientBackground, GlassCard, GlassButton, DecorativeSeparator } from "../../src/components/ui";
|
||||||
import { useAmbientOrbAnimation } from "../../src/hooks";
|
import { useAmbientOrbAnimation } from "../../src/hooks";
|
||||||
import { colors, gradients, spacing, typography, radius, shadows } from "../../src/theme";
|
import { colors, gradients, spacing, typography, radius, shadows } from "../../src/theme";
|
||||||
import { MatchesTutorial } from "../../src/components/tutorials";
|
import { MatchesTutorial } from "../../src/components/tutorials";
|
||||||
import { hasSeenMatchesTutorial, markMatchesTutorialSeen } from "../../src/lib/matchesTutorialSeen";
|
import { hasSeenMatchesTutorial, markMatchesTutorialSeen } from "../../src/lib/matchesTutorialSeen";
|
||||||
@@ -29,11 +29,11 @@ export default function MatchesScreen() {
|
|||||||
const { matches, fetchMatches, markAllAsSeen } = useMatchStore();
|
const { matches, fetchMatches, markAllAsSeen } = useMatchStore();
|
||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const isWideScreen = width > MAX_CONTENT_WIDTH;
|
const isWideScreen = width > MAX_CONTENT_WIDTH;
|
||||||
const [showTutorial, setShowTutorial] = useState(false);
|
const [showTutorial, setShowTutorial] = useState(false);
|
||||||
|
|
||||||
// Ambient orb breathing animations
|
// Ambient orb breathing animations
|
||||||
const { orbStyle1, orbStyle2 } = useAmbientOrbAnimation();
|
const { orbStyle1, orbStyle2 } = useAmbientOrbAnimation();
|
||||||
|
|
||||||
// Check if tutorial should be shown when screen is focused or matches change
|
// Check if tutorial should be shown when screen is focused or matches change
|
||||||
@@ -101,15 +101,15 @@ export default function MatchesScreen() {
|
|||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 1 }}
|
end={{ x: 1, y: 1 }}
|
||||||
/>
|
/>
|
||||||
{/* Top silk highlight */}
|
{/* Top silk highlight */}
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[`${ACCENT_RGBA}0.06)`, 'transparent']}
|
colors={[`${ACCENT_RGBA}0.06)`, 'transparent']}
|
||||||
style={styles.cardSilkHighlight}
|
style={styles.cardSilkHighlight}
|
||||||
start={{ x: 0.5, y: 0 }}
|
start={{ x: 0.5, y: 0 }}
|
||||||
end={{ x: 0.5, y: 1 }}
|
end={{ x: 0.5, y: 1 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.matchRow}>
|
<View style={styles.matchRow}>
|
||||||
{/* Premium icon container */}
|
{/* Premium icon container */}
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.iconContainerPremium,
|
styles.iconContainerPremium,
|
||||||
@@ -240,26 +240,6 @@ export default function MatchesScreen() {
|
|||||||
entering={FadeInUp.duration(600).springify()}
|
entering={FadeInUp.duration(600).springify()}
|
||||||
style={styles.emptyContent}
|
style={styles.emptyContent}
|
||||||
>
|
>
|
||||||
{/* Premium icon */}
|
|
||||||
<View style={styles.emptyIconContainerPremium}>
|
|
||||||
<Ionicons name="heart-outline" size={36} color={ROSE} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Title section */}
|
|
||||||
<Text style={styles.emptyLabel}>BEGIN</Text>
|
|
||||||
<Text style={styles.emptyTitlePremium}>No Matches Yet</Text>
|
|
||||||
|
|
||||||
{/* Decorative separator */}
|
|
||||||
<DecorativeSeparator variant="rose" />
|
|
||||||
|
|
||||||
{/* Status badge */}
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeIn.delay(300).duration(400)}
|
|
||||||
style={styles.emptyBadge}
|
|
||||||
>
|
|
||||||
<Text style={styles.emptyBadgeText}>START EXPLORING</Text>
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<Text style={styles.emptyDescription}>
|
<Text style={styles.emptyDescription}>
|
||||||
Answer questions together to discover what you both enjoy. Matches appear when you agree!
|
Answer questions together to discover what you both enjoy. Matches appear when you agree!
|
||||||
@@ -268,15 +248,15 @@ export default function MatchesScreen() {
|
|||||||
{/* Feature hints */}
|
{/* Feature hints */}
|
||||||
<View style={styles.emptyFeatures}>
|
<View style={styles.emptyFeatures}>
|
||||||
<View style={styles.emptyFeatureItem}>
|
<View style={styles.emptyFeatureItem}>
|
||||||
<Ionicons name="heart" size={16} color={ROSE} />
|
<Ionicons name="heart" size={16} color={ACCENT} />
|
||||||
<Text style={styles.emptyFeatureText}>Swipe right for yes</Text>
|
<Text style={styles.emptyFeatureText}>Swipe right for yes</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.emptyFeatureItem}>
|
<View style={styles.emptyFeatureItem}>
|
||||||
<Ionicons name="sparkles" size={16} color={ROSE} />
|
<Ionicons name="sparkles" size={16} color={ACCENT} />
|
||||||
<Text style={styles.emptyFeatureText}>Match when you both agree</Text>
|
<Text style={styles.emptyFeatureText}>Match when you both agree</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.emptyFeatureItem}>
|
<View style={styles.emptyFeatureItem}>
|
||||||
<Ionicons name="chatbubbles-outline" size={16} color={ROSE} />
|
<Ionicons name="chatbubbles-outline" size={16} color={ACCENT} />
|
||||||
<Text style={styles.emptyFeatureText}>Chat about your matches</Text>
|
<Text style={styles.emptyFeatureText}>Chat about your matches</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -295,13 +275,13 @@ export default function MatchesScreen() {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Matches Tutorial Overlay */}
|
{/* Matches Tutorial Overlay */}
|
||||||
{showTutorial && (
|
{showTutorial && (
|
||||||
<MatchesTutorial onComplete={handleTutorialComplete} />
|
<MatchesTutorial onComplete={handleTutorialComplete} />
|
||||||
)}
|
)}
|
||||||
</GradientBackground>
|
</GradientBackground>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
@@ -387,13 +367,13 @@ const styles = StyleSheet.create({
|
|||||||
maxWidth: MAX_CONTENT_WIDTH,
|
maxWidth: MAX_CONTENT_WIDTH,
|
||||||
},
|
},
|
||||||
// Premium Match Card
|
// Premium Match Card
|
||||||
matchCardPremium: {
|
matchCardPremium: {
|
||||||
marginBottom: spacing.sm,
|
marginBottom: spacing.sm,
|
||||||
borderRadius: radius.lg,
|
borderRadius: radius.lg,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
padding: spacing.md,
|
padding: spacing.md,
|
||||||
},
|
},
|
||||||
cardSilkHighlight: {
|
cardSilkHighlight: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -507,45 +487,6 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: spacing.md,
|
paddingHorizontal: spacing.md,
|
||||||
paddingVertical: spacing.xl,
|
paddingVertical: spacing.xl,
|
||||||
},
|
},
|
||||||
emptyIconContainerPremium: {
|
|
||||||
width: 72,
|
|
||||||
height: 72,
|
|
||||||
borderRadius: 36,
|
|
||||||
backgroundColor: `${ROSE_RGBA}0.1)`,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: `${ROSE_RGBA}0.2)`,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: spacing.xl,
|
|
||||||
},
|
|
||||||
emptyLabel: {
|
|
||||||
...typography.caption1,
|
|
||||||
fontWeight: '600',
|
|
||||||
letterSpacing: 3,
|
|
||||||
color: ROSE,
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: spacing.xs,
|
|
||||||
},
|
|
||||||
emptyTitlePremium: {
|
|
||||||
...typography.largeTitle,
|
|
||||||
color: colors.text,
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
emptyBadge: {
|
|
||||||
backgroundColor: `${ROSE_RGBA}0.1)`,
|
|
||||||
paddingHorizontal: spacing.lg,
|
|
||||||
paddingVertical: spacing.sm,
|
|
||||||
borderRadius: radius.full,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: `${ROSE_RGBA}0.2)`,
|
|
||||||
marginBottom: spacing.xl,
|
|
||||||
},
|
|
||||||
emptyBadgeText: {
|
|
||||||
...typography.caption2,
|
|
||||||
fontWeight: '600',
|
|
||||||
letterSpacing: 2,
|
|
||||||
color: ROSE,
|
|
||||||
},
|
|
||||||
emptyDescription: {
|
emptyDescription: {
|
||||||
...typography.body,
|
...typography.body,
|
||||||
color: colors.textSecondary,
|
color: colors.textSecondary,
|
||||||
|
|||||||
@@ -6,6 +6,34 @@ const corsHeaders = {
|
|||||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ExpoPushMessage {
|
||||||
|
to: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
sound?: string;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendExpoPushNotifications(messages: ExpoPushMessage[]) {
|
||||||
|
if (messages.length === 0) return;
|
||||||
|
|
||||||
|
const response = await fetch("https://exp.host/--/api/v2/push/send", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(messages),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
console.error("Expo push error:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
Deno.serve(async (req) => {
|
||||||
// Handle CORS preflight
|
// Handle CORS preflight
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
@@ -62,6 +90,14 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const coupleId = profile.couple_id;
|
const coupleId = profile.couple_id;
|
||||||
|
|
||||||
|
// Get partner's push token BEFORE clearing data
|
||||||
|
const { data: partnerProfile } = await supabase
|
||||||
|
.from("profiles")
|
||||||
|
.select("push_token")
|
||||||
|
.eq("couple_id", coupleId)
|
||||||
|
.neq("id", user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
// Get all match IDs for this couple (needed for storage cleanup)
|
// Get all match IDs for this couple (needed for storage cleanup)
|
||||||
const { data: matches } = await supabase
|
const { data: matches } = await supabase
|
||||||
.from("matches")
|
.from("matches")
|
||||||
@@ -112,6 +148,17 @@ Deno.serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify partner about the deletion
|
||||||
|
if (partnerProfile?.push_token) {
|
||||||
|
await sendExpoPushNotifications([{
|
||||||
|
to: partnerProfile.push_token,
|
||||||
|
title: "Relationship Deleted",
|
||||||
|
body: "Your partner has deleted all shared data. You can start fresh with a new partner anytime.",
|
||||||
|
sound: "default",
|
||||||
|
data: { type: "relationship_deleted" },
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: true, message: "Relationship data deleted successfully" }),
|
JSON.stringify({ success: true, message: "Relationship data deleted successfully" }),
|
||||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
|||||||
@@ -10,6 +10,34 @@ interface JoinCoupleBody {
|
|||||||
invite_code: string;
|
invite_code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExpoPushMessage {
|
||||||
|
to: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
sound?: string;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendExpoPushNotifications(messages: ExpoPushMessage[]) {
|
||||||
|
if (messages.length === 0) return;
|
||||||
|
|
||||||
|
const response = await fetch("https://exp.host/--/api/v2/push/send", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(messages),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
console.error("Expo push error:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
Deno.serve(async (req) => {
|
||||||
// Handle CORS preflight
|
// Handle CORS preflight
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
@@ -183,18 +211,13 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const coupleId = profile.couple_id;
|
const coupleId = profile.couple_id;
|
||||||
|
|
||||||
// Count members in this couple
|
// Get partner's push token BEFORE clearing couple_id
|
||||||
const { count, error: countError } = await supabase
|
const { data: partnerProfile } = await supabase
|
||||||
.from("profiles")
|
.from("profiles")
|
||||||
.select("*", { count: "exact", head: true })
|
.select("push_token")
|
||||||
.eq("couple_id", coupleId);
|
.eq("couple_id", coupleId)
|
||||||
|
.neq("id", user.id)
|
||||||
if (countError) {
|
.maybeSingle();
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: "Failed to check couple members" }),
|
|
||||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove ALL members from couple (both partners get unlinked)
|
// Remove ALL members from couple (both partners get unlinked)
|
||||||
// This ensures the remaining partner isn't left in a "waiting" state
|
// This ensures the remaining partner isn't left in a "waiting" state
|
||||||
@@ -221,6 +244,17 @@ Deno.serve(async (req) => {
|
|||||||
// Don't fail the request, users already unlinked
|
// Don't fail the request, users already unlinked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify partner about the unpair
|
||||||
|
if (partnerProfile?.push_token) {
|
||||||
|
await sendExpoPushNotifications([{
|
||||||
|
to: partnerProfile.push_token,
|
||||||
|
title: "Partner Disconnected",
|
||||||
|
body: "Your partner has ended the connection. You can invite a new partner anytime.",
|
||||||
|
sound: "default",
|
||||||
|
data: { type: "unpair" },
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: true, couple_deleted: true }),
|
JSON.stringify({ success: true, couple_deleted: true }),
|
||||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
|||||||
@@ -6,6 +6,34 @@ const corsHeaders = {
|
|||||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ExpoPushMessage {
|
||||||
|
to: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
sound?: string;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendExpoPushNotifications(messages: ExpoPushMessage[]) {
|
||||||
|
if (messages.length === 0) return;
|
||||||
|
|
||||||
|
const response = await fetch("https://exp.host/--/api/v2/push/send", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(messages),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
console.error("Expo push error:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
Deno.serve(async (req) => {
|
||||||
// Handle CORS preflight
|
// Handle CORS preflight
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
@@ -62,6 +90,14 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const coupleId = profile.couple_id;
|
const coupleId = profile.couple_id;
|
||||||
|
|
||||||
|
// Get partner's push token BEFORE clearing data
|
||||||
|
const { data: partnerProfile } = await supabase
|
||||||
|
.from("profiles")
|
||||||
|
.select("push_token")
|
||||||
|
.eq("couple_id", coupleId)
|
||||||
|
.neq("id", user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
// Get all match IDs for this couple (needed for storage cleanup)
|
// Get all match IDs for this couple (needed for storage cleanup)
|
||||||
const { data: matches } = await supabase
|
const { data: matches } = await supabase
|
||||||
.from("matches")
|
.from("matches")
|
||||||
@@ -112,6 +148,17 @@ Deno.serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify partner about the progress reset
|
||||||
|
if (partnerProfile?.push_token) {
|
||||||
|
await sendExpoPushNotifications([{
|
||||||
|
to: partnerProfile.push_token,
|
||||||
|
title: "Progress Reset",
|
||||||
|
body: "Your partner has reset all progress. Your matches and chats have been cleared to start fresh.",
|
||||||
|
sound: "default",
|
||||||
|
data: { type: "progress_reset" },
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: true, message: "Progress reset successfully" }),
|
JSON.stringify({ success: true, message: "Progress reset successfully" }),
|
||||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
|||||||
Reference in new issue
Block a user