diff --git a/apps/admin/src/pages/activity/UserActivityPage.tsx b/apps/admin/src/pages/activity/UserActivityPage.tsx index 4013529..fd5d612 100644 --- a/apps/admin/src/pages/activity/UserActivityPage.tsx +++ b/apps/admin/src/pages/activity/UserActivityPage.tsx @@ -30,8 +30,6 @@ import { Heart, MessageCircle, UserPlus, - Image as ImageIcon, - Video as VideoIcon, ExternalLink, } from 'lucide-react'; @@ -546,7 +544,6 @@ export function UserActivityPage() { User - Message Context Time Actions @@ -555,13 +552,13 @@ export function UserActivityPage() { {loading.messages ? ( - +
) : messages.length === 0 ? ( - + No messages yet. @@ -569,21 +566,6 @@ export function UserActivityPage() { messages.map((item) => ( {renderUserLink(item.profile)} - - {item.media_path ? ( - - {item.media_type === 'video' ? ( - <> [Video] - ) : ( - <> [Image] - )} - - ) : ( - - {item.content || Empty message} - - )} - {item.match?.question?.text || 'Unknown match'} diff --git a/apps/mobile/app/(app)/matches.tsx b/apps/mobile/app/(app)/matches.tsx index 93b0f09..5953fb2 100755 --- a/apps/mobile/app/(app)/matches.tsx +++ b/apps/mobile/app/(app)/matches.tsx @@ -11,8 +11,8 @@ import Animated, { FadeInRight, FadeInUp, } from "react-native-reanimated"; -import { GradientBackground, GlassCard, GlassButton, DecorativeSeparator } from "../../src/components/ui"; -import { useAmbientOrbAnimation } from "../../src/hooks"; +import { GradientBackground, GlassCard, GlassButton, DecorativeSeparator } from "../../src/components/ui"; +import { useAmbientOrbAnimation } from "../../src/hooks"; import { colors, gradients, spacing, typography, radius, shadows } from "../../src/theme"; import { MatchesTutorial } from "../../src/components/tutorials"; import { hasSeenMatchesTutorial, markMatchesTutorialSeen } from "../../src/lib/matchesTutorialSeen"; @@ -29,11 +29,11 @@ export default function MatchesScreen() { const { matches, fetchMatches, markAllAsSeen } = useMatchStore(); const { user } = useAuthStore(); const router = useRouter(); - const { width } = useWindowDimensions(); - const isWideScreen = width > MAX_CONTENT_WIDTH; - const [showTutorial, setShowTutorial] = useState(false); - - // Ambient orb breathing animations + const { width } = useWindowDimensions(); + const isWideScreen = width > MAX_CONTENT_WIDTH; + const [showTutorial, setShowTutorial] = useState(false); + + // Ambient orb breathing animations const { orbStyle1, orbStyle2 } = useAmbientOrbAnimation(); // 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 }} end={{ x: 1, y: 1 }} /> - {/* Top silk highlight */} - - - + {/* Top silk highlight */} + + + {/* Premium icon container */} - {/* Premium icon */} - - - - - {/* Title section */} - BEGIN - No Matches Yet - - {/* Decorative separator */} - - - {/* Status badge */} - - START EXPLORING - - {/* Description */} Answer questions together to discover what you both enjoy. Matches appear when you agree! @@ -268,15 +248,15 @@ export default function MatchesScreen() { {/* Feature hints */} - + Swipe right for yes - + Match when you both agree - + Chat about your matches @@ -295,13 +275,13 @@ export default function MatchesScreen() { /> - {/* Matches Tutorial Overlay */} - {showTutorial && ( - - )} - - ); -} + {/* Matches Tutorial Overlay */} + {showTutorial && ( + + )} + + ); +} const styles = StyleSheet.create({ container: { @@ -387,13 +367,13 @@ const styles = StyleSheet.create({ maxWidth: MAX_CONTENT_WIDTH, }, // Premium Match Card - matchCardPremium: { - marginBottom: spacing.sm, - borderRadius: radius.lg, - overflow: 'hidden', - padding: spacing.md, - }, - cardSilkHighlight: { + matchCardPremium: { + marginBottom: spacing.sm, + borderRadius: radius.lg, + overflow: 'hidden', + padding: spacing.md, + }, + cardSilkHighlight: { position: 'absolute', top: 0, left: 0, @@ -507,45 +487,6 @@ const styles = StyleSheet.create({ paddingHorizontal: spacing.md, 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: { ...typography.body, color: colors.textSecondary, diff --git a/apps/supabase/functions/delete-relationship/index.ts b/apps/supabase/functions/delete-relationship/index.ts index 853ebdf..b236756 100644 --- a/apps/supabase/functions/delete-relationship/index.ts +++ b/apps/supabase/functions/delete-relationship/index.ts @@ -6,6 +6,34 @@ const corsHeaders = { "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; +interface ExpoPushMessage { + to: string; + title: string; + body: string; + sound?: string; + data?: Record; +} + +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) => { // Handle CORS preflight if (req.method === "OPTIONS") { @@ -62,6 +90,14 @@ Deno.serve(async (req) => { 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) const { data: matches } = await supabase .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( JSON.stringify({ success: true, message: "Relationship data deleted successfully" }), { headers: { ...corsHeaders, "Content-Type": "application/json" } } diff --git a/apps/supabase/functions/manage-couple/index.ts b/apps/supabase/functions/manage-couple/index.ts index 78ac358..dd87c5a 100755 --- a/apps/supabase/functions/manage-couple/index.ts +++ b/apps/supabase/functions/manage-couple/index.ts @@ -10,6 +10,34 @@ interface JoinCoupleBody { invite_code: string; } +interface ExpoPushMessage { + to: string; + title: string; + body: string; + sound?: string; + data?: Record; +} + +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) => { // Handle CORS preflight if (req.method === "OPTIONS") { @@ -183,18 +211,13 @@ Deno.serve(async (req) => { const coupleId = profile.couple_id; - // Count members in this couple - const { count, error: countError } = await supabase + // Get partner's push token BEFORE clearing couple_id + const { data: partnerProfile } = await supabase .from("profiles") - .select("*", { count: "exact", head: true }) - .eq("couple_id", coupleId); - - if (countError) { - return new Response( - JSON.stringify({ error: "Failed to check couple members" }), - { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } + .select("push_token") + .eq("couple_id", coupleId) + .neq("id", user.id) + .maybeSingle(); // Remove ALL members from couple (both partners get unlinked) // 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 } + // 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( JSON.stringify({ success: true, couple_deleted: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } } diff --git a/apps/supabase/functions/reset-couple-progress/index.ts b/apps/supabase/functions/reset-couple-progress/index.ts index e961526..53dba63 100644 --- a/apps/supabase/functions/reset-couple-progress/index.ts +++ b/apps/supabase/functions/reset-couple-progress/index.ts @@ -6,6 +6,34 @@ const corsHeaders = { "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; +interface ExpoPushMessage { + to: string; + title: string; + body: string; + sound?: string; + data?: Record; +} + +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) => { // Handle CORS preflight if (req.method === "OPTIONS") { @@ -62,6 +90,14 @@ Deno.serve(async (req) => { 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) const { data: matches } = await supabase .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( JSON.stringify({ success: true, message: "Progress reset successfully" }), { headers: { ...corsHeaders, "Content-Type": "application/json" } }