mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Add show all intensities toggle on packs page
This commit is contained in:
1 parent
a0049b7f8a
commit
b8c271cce1
2 files changed
+166
-102
No files matched your search
@@ -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 { usePacksStore, useAuthStore, useSubscriptionStore } from "../../../src/store";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
@@ -258,7 +258,7 @@ function CategorySection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function QuestionPacksScreen() {
|
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 { user, partner } = useAuthStore();
|
||||||
const { subscription } = useSubscriptionStore();
|
const { subscription } = useSubscriptionStore();
|
||||||
|
|
||||||
@@ -335,6 +335,10 @@ export default function QuestionPacksScreen() {
|
|||||||
setTeaserPack(null);
|
setTeaserPack(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleComfortZone = useCallback((value: boolean) => {
|
||||||
|
setShowAllIntensities(value);
|
||||||
|
}, [setShowAllIntensities]);
|
||||||
|
|
||||||
const enabledPackCount = enabledPackIds.length;
|
const enabledPackCount = enabledPackIds.length;
|
||||||
const totalQuestions = packs
|
const totalQuestions = packs
|
||||||
.filter(p => enabledPackIds.includes(p.id))
|
.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"}
|
{totalQuestions} {totalQuestions === 1 ? "question" : "questions"} from {enabledPackCount} {enabledPackCount === 1 ? "pack" : "packs"}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</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>
|
</Animated.View>
|
||||||
|
|
||||||
{/* Categories */}
|
{/* Categories */}
|
||||||
@@ -610,6 +640,30 @@ const styles = StyleSheet.create({
|
|||||||
color: colors.textSecondary,
|
color: colors.textSecondary,
|
||||||
fontWeight: "500",
|
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
|
// Scroll Content
|
||||||
scrollContent: {
|
scrollContent: {
|
||||||
|
|||||||
+110
-100
@@ -1,7 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { supabase } from "../lib/supabase";
|
import { supabase } from "../lib/supabase";
|
||||||
import { Events } from "../lib/analytics";
|
import { Events } from "../lib/analytics";
|
||||||
import type { QuestionPack, Category } from "@/types";
|
import type { QuestionPack, Category } from "@/types";
|
||||||
import { useAuthStore } from "./authStore";
|
import { useAuthStore } from "./authStore";
|
||||||
|
|
||||||
const DEFAULT_MAX_INTENSITY = 2;
|
const DEFAULT_MAX_INTENSITY = 2;
|
||||||
@@ -21,30 +21,34 @@ const getUserMaxIntensity = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface PacksState {
|
interface PacksState {
|
||||||
packs: QuestionPack[];
|
packs: QuestionPack[];
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
enabledPackIds: string[];
|
enabledPackIds: string[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
fetchPacks: () => Promise<void>;
|
showAllIntensities: boolean;
|
||||||
fetchEnabledPacks: () => Promise<void>;
|
fetchPacks: () => Promise<void>;
|
||||||
ensureEnabledPacksLoaded: () => Promise<void>;
|
fetchEnabledPacks: () => Promise<void>;
|
||||||
togglePack: (packId: string) => Promise<{ success: boolean; reason?: string }>;
|
ensureEnabledPacksLoaded: () => Promise<void>;
|
||||||
clearPacks: () => void;
|
togglePack: (packId: string) => Promise<{ success: boolean; reason?: string }>;
|
||||||
}
|
setShowAllIntensities: (value: boolean) => void;
|
||||||
|
clearPacks: () => void;
|
||||||
export const usePacksStore = create<PacksState>((set, get) => ({
|
}
|
||||||
packs: [],
|
|
||||||
categories: [],
|
export const usePacksStore = create<PacksState>((set, get) => ({
|
||||||
enabledPackIds: [],
|
packs: [],
|
||||||
isLoading: false,
|
categories: [],
|
||||||
|
enabledPackIds: [],
|
||||||
fetchPacks: async () => {
|
isLoading: false,
|
||||||
set({ isLoading: true });
|
showAllIntensities: false,
|
||||||
|
|
||||||
|
fetchPacks: async () => {
|
||||||
|
set({ isLoading: true });
|
||||||
|
|
||||||
// Get user's max intensity preference
|
// Get user's max intensity preference
|
||||||
const maxIntensity = getUserMaxIntensity();
|
const maxIntensity = getUserMaxIntensity();
|
||||||
|
const showAllIntensities = get().showAllIntensities;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch categories
|
// Fetch categories
|
||||||
const { data: categories } = await supabase
|
const { data: categories } = await supabase
|
||||||
.from("categories")
|
.from("categories")
|
||||||
@@ -62,8 +66,8 @@ export const usePacksStore = create<PacksState>((set, get) => ({
|
|||||||
.eq("is_public", true)
|
.eq("is_public", true)
|
||||||
.order("sort_order");
|
.order("sort_order");
|
||||||
|
|
||||||
// Filter out packs above user's intensity preference
|
// Filter out packs above user's intensity preference (unless showing all)
|
||||||
if (maxIntensity < 5) {
|
if (maxIntensity < 5 && !showAllIntensities) {
|
||||||
query = query.or(`max_intensity.is.null,max_intensity.lte.${maxIntensity}`);
|
query = query.or(`max_intensity.is.null,max_intensity.lte.${maxIntensity}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,76 +90,82 @@ export const usePacksStore = create<PacksState>((set, get) => ({
|
|||||||
isLoading: false
|
isLoading: false
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchEnabledPacks: async () => {
|
fetchEnabledPacks: async () => {
|
||||||
const coupleId = useAuthStore.getState().user?.couple_id;
|
const coupleId = useAuthStore.getState().user?.couple_id;
|
||||||
if (!coupleId) {
|
if (!coupleId) {
|
||||||
set({ enabledPackIds: [] });
|
set({ enabledPackIds: [] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: couplePacks } = await supabase
|
const { data: couplePacks } = await supabase
|
||||||
.from("couple_packs")
|
.from("couple_packs")
|
||||||
.select("pack_id")
|
.select("pack_id")
|
||||||
.eq("couple_id", coupleId)
|
.eq("couple_id", coupleId)
|
||||||
.eq("enabled", true);
|
.eq("enabled", true);
|
||||||
|
|
||||||
const ids = couplePacks?.map(cp => cp.pack_id) || [];
|
const ids = couplePacks?.map(cp => cp.pack_id) || [];
|
||||||
set({ enabledPackIds: ids });
|
set({ enabledPackIds: ids });
|
||||||
},
|
},
|
||||||
|
|
||||||
ensureEnabledPacksLoaded: async () => {
|
ensureEnabledPacksLoaded: async () => {
|
||||||
// Only fetch if not already loaded - lightweight check for swipe screen
|
// Only fetch if not already loaded - lightweight check for swipe screen
|
||||||
const coupleId = useAuthStore.getState().user?.couple_id;
|
const coupleId = useAuthStore.getState().user?.couple_id;
|
||||||
if (!coupleId) return;
|
if (!coupleId) return;
|
||||||
|
|
||||||
if (get().enabledPackIds.length === 0) {
|
if (get().enabledPackIds.length === 0) {
|
||||||
await get().fetchEnabledPacks();
|
await get().fetchEnabledPacks();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePack: async (packId: string): Promise<{ success: boolean; reason?: string }> => {
|
togglePack: async (packId: string): Promise<{ success: boolean; reason?: string }> => {
|
||||||
const coupleId = useAuthStore.getState().user?.couple_id;
|
const coupleId = useAuthStore.getState().user?.couple_id;
|
||||||
if (!coupleId) {
|
if (!coupleId) {
|
||||||
return { success: false, reason: "no_couple" };
|
return { success: false, reason: "no_couple" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEnabled = get().enabledPackIds.includes(packId);
|
const isEnabled = get().enabledPackIds.includes(packId);
|
||||||
const newValue = !isEnabled;
|
const newValue = !isEnabled;
|
||||||
|
|
||||||
// Optimistic update
|
// Optimistic update
|
||||||
const newIds = newValue
|
const newIds = newValue
|
||||||
? [...get().enabledPackIds, packId]
|
? [...get().enabledPackIds, packId]
|
||||||
: get().enabledPackIds.filter(id => id !== packId);
|
: get().enabledPackIds.filter(id => id !== packId);
|
||||||
|
|
||||||
set({ enabledPackIds: newIds });
|
set({ enabledPackIds: newIds });
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from("couple_packs")
|
.from("couple_packs")
|
||||||
.upsert({
|
.upsert({
|
||||||
couple_id: coupleId,
|
couple_id: coupleId,
|
||||||
pack_id: packId,
|
pack_id: packId,
|
||||||
enabled: newValue
|
enabled: newValue
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error toggling pack:", error);
|
console.error("Error toggling pack:", error);
|
||||||
// Revert by refetching
|
// Revert by refetching
|
||||||
get().fetchEnabledPacks();
|
get().fetchEnabledPacks();
|
||||||
return { success: false, reason: "error" };
|
return { success: false, reason: "error" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track pack enable/disable
|
// Track pack enable/disable
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
Events.packEnabled(packId);
|
Events.packEnabled(packId);
|
||||||
} else {
|
} else {
|
||||||
Events.packDisabled(packId);
|
Events.packDisabled(packId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
clearPacks: () => {
|
setShowAllIntensities: (value: boolean) => {
|
||||||
set({ enabledPackIds: [] });
|
set({ showAllIntensities: value });
|
||||||
},
|
// Refetch packs with new filter setting
|
||||||
}));
|
get().fetchPacks();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearPacks: () => {
|
||||||
|
set({ enabledPackIds: [] });
|
||||||
|
},
|
||||||
|
}));
|
||||||
Reference in new issue
Block a user