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:
jjbrunton committed 2026-01-03 18:58:57 +00:00
1 parent a567fba349
commit 994dd2a295
5 files changed
+174 -123

No files matched your search

+33 -92
View File
@@ -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 */}
<LinearGradient
colors={[`${ACCENT_RGBA}0.06)`, 'transparent']}
style={styles.cardSilkHighlight}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
/>
<View style={styles.matchRow}>
{/* Top silk highlight */}
<LinearGradient
colors={[`${ACCENT_RGBA}0.06)`, 'transparent']}
style={styles.cardSilkHighlight}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
/>
<View style={styles.matchRow}>
{/* Premium icon container */}
<View style={[
styles.iconContainerPremium,
@@ -240,26 +240,6 @@ export default function MatchesScreen() {
entering={FadeInUp.duration(600).springify()}
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 */}
<Text style={styles.emptyDescription}>
Answer questions together to discover what you both enjoy. Matches appear when you agree!
@@ -268,15 +248,15 @@ export default function MatchesScreen() {
{/* Feature hints */}
<View style={styles.emptyFeatures}>
<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>
</View>
<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>
</View>
<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>
</View>
</View>
@@ -295,13 +275,13 @@ export default function MatchesScreen() {
/>
</View>
{/* Matches Tutorial Overlay */}
{showTutorial && (
<MatchesTutorial onComplete={handleTutorialComplete} />
)}
</GradientBackground>
);
}
{/* Matches Tutorial Overlay */}
{showTutorial && (
<MatchesTutorial onComplete={handleTutorialComplete} />
)}
</GradientBackground>
);
}
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,