Add show all intensities toggle on packs page

This commit is contained in:
jjbrunton committed 2026-01-17 17:12:10 +00:00
1 parent a0049b7f8a
commit b8c271cce1
2 files changed
+166 -102

No files matched your search

+56 -2
View File
@@ -1,4 +1,4 @@
import { View, Text, StyleSheet, RefreshControl, Platform, Alert, Pressable } from "react-native";
import { View, Text, StyleSheet, RefreshControl, Platform, Alert, Pressable, Switch } from "react-native";
import { usePacksStore, useAuthStore, useSubscriptionStore } from "../../../src/store";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
@@ -258,7 +258,7 @@ function CategorySection({
}
export default function QuestionPacksScreen() {
const { packs, categories, enabledPackIds, togglePack, isLoading, fetchPacks } = usePacksStore();
const { packs, categories, enabledPackIds, togglePack, isLoading, fetchPacks, showAllIntensities, setShowAllIntensities } = usePacksStore();
const { user, partner } = useAuthStore();
const { subscription } = useSubscriptionStore();
@@ -335,6 +335,10 @@ export default function QuestionPacksScreen() {
setTeaserPack(null);
}, []);
const handleToggleComfortZone = useCallback((value: boolean) => {
setShowAllIntensities(value);
}, [setShowAllIntensities]);
const enabledPackCount = enabledPackIds.length;
const totalQuestions = packs
.filter(p => enabledPackIds.includes(p.id))
@@ -442,6 +446,32 @@ export default function QuestionPacksScreen() {
{totalQuestions} {totalQuestions === 1 ? "question" : "questions"} from {enabledPackCount} {enabledPackCount === 1 ? "pack" : "packs"}
</Text>
</View>
<Pressable
style={styles.comfortZoneToggle}
onPress={() => handleToggleComfortZone(!showAllIntensities)}
>
<View style={styles.comfortZoneToggleLeft}>
<Ionicons
name={showAllIntensities ? "flame" : "flame-outline"}
size={16}
color={showAllIntensities ? colors.primary : colors.textSecondary}
/>
<Text style={styles.comfortZoneToggleText}>
Show all intensities
</Text>
</View>
<Switch
value={showAllIntensities}
onValueChange={handleToggleComfortZone}
trackColor={{
false: "rgba(255, 255, 255, 0.1)",
true: "rgba(233, 69, 96, 0.4)"
}}
thumbColor={showAllIntensities ? colors.primary : colors.textTertiary}
ios_backgroundColor="rgba(255, 255, 255, 0.1)"
/>
</Pressable>
</Animated.View>
{/* Categories */}
@@ -610,6 +640,30 @@ const styles = StyleSheet.create({
color: colors.textSecondary,
fontWeight: "500",
},
comfortZoneToggle: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "rgba(255, 255, 255, 0.05)",
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: radius.md,
borderWidth: 1,
borderColor: colors.glass.border,
marginTop: spacing.md,
width: "100%",
maxWidth: 280,
},
comfortZoneToggleLeft: {
flexDirection: "row",
alignItems: "center",
gap: spacing.sm,
},
comfortZoneToggleText: {
...typography.subhead,
color: colors.textSecondary,
fontWeight: "500",
},
// Scroll Content
scrollContent: {
+110 -100
View File
@@ -1,7 +1,7 @@
import { create } from "zustand";
import { supabase } from "../lib/supabase";
import { Events } from "../lib/analytics";
import type { QuestionPack, Category } from "@/types";
import { create } from "zustand";
import { supabase } from "../lib/supabase";
import { Events } from "../lib/analytics";
import type { QuestionPack, Category } from "@/types";
import { useAuthStore } from "./authStore";
const DEFAULT_MAX_INTENSITY = 2;
@@ -21,30 +21,34 @@ const getUserMaxIntensity = () => {
};
interface PacksState {
packs: QuestionPack[];
categories: Category[];
enabledPackIds: string[];
isLoading: boolean;
fetchPacks: () => Promise<void>;
fetchEnabledPacks: () => Promise<void>;
ensureEnabledPacksLoaded: () => Promise<void>;
togglePack: (packId: string) => Promise<{ success: boolean; reason?: string }>;
clearPacks: () => void;
}
export const usePacksStore = create<PacksState>((set, get) => ({
packs: [],
categories: [],
enabledPackIds: [],
isLoading: false,
fetchPacks: async () => {
set({ isLoading: true });
packs: QuestionPack[];
categories: Category[];
enabledPackIds: string[];
isLoading: boolean;
showAllIntensities: boolean;
fetchPacks: () => Promise<void>;
fetchEnabledPacks: () => Promise<void>;
ensureEnabledPacksLoaded: () => Promise<void>;
togglePack: (packId: string) => Promise<{ success: boolean; reason?: string }>;
setShowAllIntensities: (value: boolean) => void;
clearPacks: () => void;
}
export const usePacksStore = create<PacksState>((set, get) => ({
packs: [],
categories: [],
enabledPackIds: [],
isLoading: false,
showAllIntensities: false,
fetchPacks: async () => {
set({ isLoading: true });
// Get user's max intensity preference
const maxIntensity = getUserMaxIntensity();
const showAllIntensities = get().showAllIntensities;
// Fetch categories
const { data: categories } = await supabase
.from("categories")
@@ -62,8 +66,8 @@ export const usePacksStore = create<PacksState>((set, get) => ({
.eq("is_public", true)
.order("sort_order");
// Filter out packs above user's intensity preference
if (maxIntensity < 5) {
// Filter out packs above user's intensity preference (unless showing all)
if (maxIntensity < 5 && !showAllIntensities) {
query = query.or(`max_intensity.is.null,max_intensity.lte.${maxIntensity}`);
}
@@ -86,76 +90,82 @@ export const usePacksStore = create<PacksState>((set, get) => ({
isLoading: false
});
},
fetchEnabledPacks: async () => {
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) {
set({ enabledPackIds: [] });
return;
}
const { data: couplePacks } = await supabase
.from("couple_packs")
.select("pack_id")
.eq("couple_id", coupleId)
.eq("enabled", true);
const ids = couplePacks?.map(cp => cp.pack_id) || [];
set({ enabledPackIds: ids });
},
ensureEnabledPacksLoaded: async () => {
// Only fetch if not already loaded - lightweight check for swipe screen
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) return;
if (get().enabledPackIds.length === 0) {
await get().fetchEnabledPacks();
}
},
togglePack: async (packId: string): Promise<{ success: boolean; reason?: string }> => {
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) {
return { success: false, reason: "no_couple" };
}
const isEnabled = get().enabledPackIds.includes(packId);
const newValue = !isEnabled;
// Optimistic update
const newIds = newValue
? [...get().enabledPackIds, packId]
: get().enabledPackIds.filter(id => id !== packId);
set({ enabledPackIds: newIds });
const { error } = await supabase
.from("couple_packs")
.upsert({
couple_id: coupleId,
pack_id: packId,
enabled: newValue
});
if (error) {
console.error("Error toggling pack:", error);
// Revert by refetching
get().fetchEnabledPacks();
return { success: false, reason: "error" };
}
// Track pack enable/disable
if (newValue) {
Events.packEnabled(packId);
} else {
Events.packDisabled(packId);
}
return { success: true };
},
clearPacks: () => {
set({ enabledPackIds: [] });
},
}));
fetchEnabledPacks: async () => {
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) {
set({ enabledPackIds: [] });
return;
}
const { data: couplePacks } = await supabase
.from("couple_packs")
.select("pack_id")
.eq("couple_id", coupleId)
.eq("enabled", true);
const ids = couplePacks?.map(cp => cp.pack_id) || [];
set({ enabledPackIds: ids });
},
ensureEnabledPacksLoaded: async () => {
// Only fetch if not already loaded - lightweight check for swipe screen
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) return;
if (get().enabledPackIds.length === 0) {
await get().fetchEnabledPacks();
}
},
togglePack: async (packId: string): Promise<{ success: boolean; reason?: string }> => {
const coupleId = useAuthStore.getState().user?.couple_id;
if (!coupleId) {
return { success: false, reason: "no_couple" };
}
const isEnabled = get().enabledPackIds.includes(packId);
const newValue = !isEnabled;
// Optimistic update
const newIds = newValue
? [...get().enabledPackIds, packId]
: get().enabledPackIds.filter(id => id !== packId);
set({ enabledPackIds: newIds });
const { error } = await supabase
.from("couple_packs")
.upsert({
couple_id: coupleId,
pack_id: packId,
enabled: newValue
});
if (error) {
console.error("Error toggling pack:", error);
// Revert by refetching
get().fetchEnabledPacks();
return { success: false, reason: "error" };
}
// Track pack enable/disable
if (newValue) {
Events.packEnabled(packId);
} else {
Events.packDisabled(packId);
}
return { success: true };
},
setShowAllIntensities: (value: boolean) => {
set({ showAllIntensities: value });
// Refetch packs with new filter setting
get().fetchPacks();
},
clearPacks: () => {
set({ enabledPackIds: [] });
},
}));