mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +00:00
Add question types, nudge feature, and share modal
- Add new question card components (photo, audio, text, who-likely) - Add share preview modal for matches - Add nudge notification system with rate limiting - Add session-based notification improvements - Add match confetti celebration animation - Update onboarding flow with paywall tracking - Remove deprecated swipe card and tutorial components - Update GlassCard/GlassInput to use solid backgrounds - Fix routing issues after packs screen removal
This commit is contained in:
1 parent
8612b16b93
commit
1daf386765
114 files changed
+14877
-7911
No files matched your search
@@ -60,14 +60,14 @@ export async function getCachedSignedUrl(
|
||||
/**
|
||||
* Extract storage path from media_path (handles both old full URLs and new path-only format)
|
||||
*/
|
||||
export 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;
|
||||
}
|
||||
export function getStoragePath(mediaPath: string): string {
|
||||
if (mediaPath.startsWith('http')) {
|
||||
// Old format: extract path from full URL
|
||||
const match = mediaPath.match(/\/(chat-media|response-media)\/(.+)$/);
|
||||
return match ? match[2] : mediaPath;
|
||||
}
|
||||
return mediaPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear expired URLs from the cache to free memory.
|
||||
|
||||
@@ -5,6 +5,7 @@ import { router } from "expo-router";
|
||||
import { supabase } from "./supabase";
|
||||
import { captureError } from "./sentry";
|
||||
import { Events } from "./analytics";
|
||||
import { useMatchStore } from "../store/matchStore";
|
||||
|
||||
// Configure notification behavior for foreground
|
||||
// We don't show system alerts since the app handles notifications with in-app toasts
|
||||
@@ -17,13 +18,13 @@ Notifications.setNotificationHandler({
|
||||
});
|
||||
|
||||
// Types
|
||||
export interface NotificationData {
|
||||
type: "match" | "message" | "match_digest";
|
||||
match_id?: string;
|
||||
message_id?: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface NotificationData {
|
||||
type: "match" | "message" | "match_digest" | "partner_activity" | "nudge";
|
||||
match_id?: string;
|
||||
message_id?: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Request notification permissions and get Expo push token.
|
||||
@@ -221,28 +222,42 @@ export async function checkAndRegisterPushToken(userId: string): Promise<boolean
|
||||
/**
|
||||
* Handle notification tap - navigate to appropriate screen.
|
||||
*/
|
||||
export function handleNotificationResponse(
|
||||
response: Notifications.NotificationResponse
|
||||
): void {
|
||||
const data = response.notification.request.content.data as NotificationData;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === "match_digest") {
|
||||
router.push("/(app)/matches");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.match_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Both match and message notifications navigate to chat
|
||||
router.push(`/(app)/chat/${data.match_id}`);
|
||||
}
|
||||
|
||||
export function handleNotificationResponse(
|
||||
response: Notifications.NotificationResponse
|
||||
): void {
|
||||
const data = response.notification.request.content.data as NotificationData;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === "match_digest") {
|
||||
router.push("/(app)/matches");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === "nudge") {
|
||||
// Navigate to Matches > Your Turn tab
|
||||
useMatchStore.getState().setCurrentView('pending');
|
||||
router.push("/(app)/matches");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === "partner_activity") {
|
||||
// Navigate to Matches > Your Turn tab (questions to answer)
|
||||
useMatchStore.getState().setCurrentView('pending');
|
||||
router.push("/(app)/matches");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.match_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Both match and message notifications navigate to chat
|
||||
router.push(`/(app)/chat/${data.match_id}`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up listener for notification taps (when app is running).
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
const STORAGE_KEY_PREFIX = "onboarding_paywall_seen";
|
||||
|
||||
const buildKey = (userId: string): string => `${STORAGE_KEY_PREFIX}_${userId}`;
|
||||
|
||||
async function getStorage(key: string): Promise<boolean> {
|
||||
try {
|
||||
let data: string | null = null;
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
||||
data = window.localStorage.getItem(key);
|
||||
}
|
||||
} else {
|
||||
data = await SecureStore.getItemAsync(key);
|
||||
}
|
||||
|
||||
return data === "true";
|
||||
} catch (error) {
|
||||
console.error("Error reading onboarding paywall state:", error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function setStorage(key: string, seen: boolean): Promise<void> {
|
||||
try {
|
||||
const data = seen ? "true" : "false";
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
||||
window.localStorage.setItem(key, data);
|
||||
}
|
||||
} else {
|
||||
await SecureStore.setItemAsync(key, data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error saving onboarding paywall state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function hasSeenOnboardingPaywall(userId: string): Promise<boolean> {
|
||||
return getStorage(buildKey(userId));
|
||||
}
|
||||
|
||||
export async function markOnboardingPaywallSeen(userId: string): Promise<void> {
|
||||
await setStorage(buildKey(userId), true);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class RevenueCatService {
|
||||
return result.current;
|
||||
}
|
||||
|
||||
async getOfferingsDebug(): Promise<{
|
||||
async getOfferingsDebug(offeringIdentifier?: string): Promise<{
|
||||
current: PurchasesOffering | null;
|
||||
availableOfferings: string[] | null;
|
||||
error: string | null;
|
||||
@@ -162,8 +162,17 @@ class RevenueCatService {
|
||||
const offerings = await this.Purchases.getOfferings();
|
||||
const availableOfferings = Object.keys(offerings.all || {});
|
||||
|
||||
// If a specific offering is requested, try to get it
|
||||
let targetOffering = offerings.current;
|
||||
if (offeringIdentifier && offerings.all?.[offeringIdentifier]) {
|
||||
console.log(`RevenueCat: Using offering "${offeringIdentifier}"`);
|
||||
targetOffering = offerings.all[offeringIdentifier];
|
||||
} else if (offeringIdentifier) {
|
||||
console.log(`RevenueCat: Offering "${offeringIdentifier}" not found, falling back to current`);
|
||||
}
|
||||
|
||||
return {
|
||||
current: offerings.current,
|
||||
current: targetOffering,
|
||||
availableOfferings,
|
||||
error: null
|
||||
};
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
const STORAGE_KEY = "swipe_tutorial_seen";
|
||||
|
||||
async function getStorage(): Promise<boolean> {
|
||||
try {
|
||||
let data: string | null = null;
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
||||
data = window.localStorage.getItem(STORAGE_KEY);
|
||||
}
|
||||
} else {
|
||||
data = await SecureStore.getItemAsync(STORAGE_KEY);
|
||||
}
|
||||
|
||||
return data === "true";
|
||||
} catch (error) {
|
||||
console.error("Error reading swipe tutorial state:", error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function setStorage(seen: boolean): Promise<void> {
|
||||
try {
|
||||
const data = seen ? "true" : "false";
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
||||
window.localStorage.setItem(STORAGE_KEY, data);
|
||||
}
|
||||
} else {
|
||||
await SecureStore.setItemAsync(STORAGE_KEY, data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error saving swipe tutorial state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function hasSeenSwipeTutorial(): Promise<boolean> {
|
||||
return getStorage();
|
||||
}
|
||||
|
||||
export async function markSwipeTutorialSeen(): Promise<void> {
|
||||
await setStorage(true);
|
||||
}
|
||||
|
||||
export async function resetSwipeTutorial(): Promise<void> {
|
||||
await setStorage(false);
|
||||
}
|
||||
Reference in new issue
Block a user