Skip to content

Commit f7638fd

Browse files
Migrate ad preference management from local storage to Convex
Co-authored-by: tannerlinsley <tannerlinsley@gmail.com>
1 parent 3689791 commit f7638fd

5 files changed

Lines changed: 160 additions & 50 deletions

File tree

convex/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const schema = defineSchema({
3131
capabilities: v.array(
3232
v.union(...validCapabilities.map((cap) => v.literal(cap)))
3333
),
34+
adsDisabled: v.optional(v.boolean()),
3435
}),
3536
})
3637

convex/users.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,71 @@ async function requireCapability(ctx: QueryCtx, capability: Capability) {
8484

8585
return { currentUser }
8686
}
87+
88+
// Get current user's ad preference
89+
export const getUserAdPreference = query({
90+
args: {},
91+
handler: async (ctx) => {
92+
const currentUser = await getCurrentUserConvex(ctx)
93+
if (!currentUser) {
94+
throw new Error('Not authenticated')
95+
}
96+
97+
return {
98+
adsDisabled: currentUser.adsDisabled ?? false,
99+
canDisableAds: currentUser.capabilities.includes('disableAds'),
100+
}
101+
},
102+
})
103+
104+
// Toggle ad preference (only for users with disableAds capability)
105+
export const toggleAdPreference = mutation({
106+
args: {},
107+
handler: async (ctx) => {
108+
const currentUser = await getCurrentUserConvex(ctx)
109+
if (!currentUser) {
110+
throw new Error('Not authenticated')
111+
}
112+
113+
// Check if user has capability to disable ads
114+
if (!currentUser.capabilities.includes('disableAds')) {
115+
throw new Error('User does not have permission to disable ads')
116+
}
117+
118+
const currentAdsDisabled = currentUser.adsDisabled ?? false
119+
120+
await ctx.db.patch(currentUser._id, {
121+
adsDisabled: !currentAdsDisabled,
122+
})
123+
124+
return {
125+
adsDisabled: !currentAdsDisabled,
126+
}
127+
},
128+
})
129+
130+
// Set ad preference (only for users with disableAds capability)
131+
export const setAdPreference = mutation({
132+
args: {
133+
adsDisabled: v.boolean(),
134+
},
135+
handler: async (ctx, args) => {
136+
const currentUser = await getCurrentUserConvex(ctx)
137+
if (!currentUser) {
138+
throw new Error('Not authenticated')
139+
}
140+
141+
// Check if user has capability to disable ads
142+
if (!currentUser.capabilities.includes('disableAds')) {
143+
throw new Error('User does not have permission to disable ads')
144+
}
145+
146+
await ctx.db.patch(currentUser._id, {
147+
adsDisabled: args.adsDisabled,
148+
})
149+
150+
return {
151+
adsDisabled: args.adsDisabled,
152+
}
153+
},
154+
})

src/hooks/useAdPreference.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { convexQuery, useConvexMutation } from '@convex-dev/react-query'
2+
import { useQuery, useQueryClient } from '@tanstack/react-query'
3+
import { api } from 'convex/_generated/api'
4+
5+
export function useAdPreferenceQuery() {
6+
return useQuery(convexQuery(api.users.getUserAdPreference, {}))
7+
}
8+
9+
export function useToggleAdPreference() {
10+
const queryClient = useQueryClient()
11+
12+
return useConvexMutation(api.users.toggleAdPreference, {
13+
onSuccess: () => {
14+
// Invalidate and refetch ad preference query
15+
queryClient.invalidateQueries({
16+
queryKey: ['convex', api.users.getUserAdPreference, {}],
17+
})
18+
// Also invalidate current user query since it might contain ad preferences
19+
queryClient.invalidateQueries({
20+
queryKey: ['convex', api.auth.getCurrentUser, {}],
21+
})
22+
},
23+
})
24+
}
25+
26+
export function useSetAdPreference() {
27+
const queryClient = useQueryClient()
28+
29+
return useConvexMutation(api.users.setAdPreference, {
30+
onSuccess: () => {
31+
// Invalidate and refetch ad preference query
32+
queryClient.invalidateQueries({
33+
queryKey: ['convex', api.users.getUserAdPreference, {}],
34+
})
35+
// Also invalidate current user query
36+
queryClient.invalidateQueries({
37+
queryKey: ['convex', api.auth.getCurrentUser, {}],
38+
})
39+
},
40+
})
41+
}
42+
43+
// Legacy hook for backward compatibility - replace the useAdsPreference function
44+
export function useAdsPreference() {
45+
const adPreferenceQuery = useAdPreferenceQuery()
46+
47+
if (adPreferenceQuery.isLoading || !adPreferenceQuery.data) {
48+
return { adsEnabled: true } // Default to showing ads while loading
49+
}
50+
51+
const { adsDisabled, canDisableAds } = adPreferenceQuery.data
52+
53+
// Ads are enabled if user can't disable them OR if they haven't disabled them
54+
const adsEnabled = !canDisableAds || !adsDisabled
55+
56+
return { adsEnabled }
57+
}

src/routes/_libraries/account.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useUserSettingsStore } from '~/stores/userSettings'
1+
import { useAdPreferenceQuery, useToggleAdPreference } from '~/hooks/useAdPreference'
22
import { FaSignOutAlt } from 'react-icons/fa'
33
import { Authenticated, Unauthenticated } from 'convex/react'
44
import { Link, redirect } from '@tanstack/react-router'
@@ -11,10 +11,17 @@ export const Route = createFileRoute({
1111

1212
function UserSettings() {
1313
const userQuery = useCurrentUserQuery()
14-
const adsDisabled = useUserSettingsStore((s) => s.settings.adsDisabled)
15-
const toggleAds = useUserSettingsStore((s) => s.toggleAds)
16-
17-
const canDisableAds = userQuery.data?.capabilities.includes('disableAds')
14+
// Replace local storage-based state with Convex-based queries
15+
const adPreferenceQuery = useAdPreferenceQuery()
16+
const toggleAdPreferenceMutation = useToggleAdPreference()
17+
18+
// Get values from the new queries
19+
const adsDisabled = adPreferenceQuery.data?.adsDisabled ?? false
20+
const canDisableAds = adPreferenceQuery.data?.canDisableAds ?? false
21+
22+
const handleToggleAds = () => {
23+
toggleAdPreferenceMutation.mutate()
24+
}
1825

1926
const signOut = async () => {
2027
await authClient.signOut()
@@ -53,8 +60,8 @@ function UserSettings() {
5360
type="checkbox"
5461
className="h-4 w-4 accent-blue-600 my-1"
5562
checked={adsDisabled}
56-
onChange={toggleAds}
57-
disabled={userQuery.isLoading}
63+
onChange={handleToggleAds}
64+
disabled={adPreferenceQuery.isLoading || toggleAdPreferenceMutation.isPending}
5865
aria-label="Disable Ads"
5966
/>
6067
<div>

src/stores/userSettings.ts

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,32 @@
11
import { create } from 'zustand'
2-
import { persist, createJSONStorage } from 'zustand/middleware'
3-
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
2+
// Remove persist and createJSONStorage imports since we no longer use localStorage
3+
// import { persist, createJSONStorage } from 'zustand/middleware'
4+
// Remove the useCurrentUserQuery import since we'll use the new hook
5+
// import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
6+
7+
// Update the export to use the new hook
8+
export { useAdsPreference } from '~/hooks/useAdPreference'
49

510
export type UserSettings = {
6-
adsDisabled: boolean
11+
// Remove adsDisabled since it's now handled by Convex
12+
// Other settings can be added here in the future
713
}
814

915
type UserSettingsState = {
1016
settings: UserSettings
1117
hasHydrated: boolean
1218
setHasHydrated: (value: boolean) => void
13-
toggleAds: () => void
19+
// Remove toggleAds since it's now handled by the new hooks
1420
}
1521

16-
export const useUserSettingsStore = create<UserSettingsState>()(
17-
persist(
18-
(set, get) => ({
19-
settings: {
20-
adsDisabled: false,
21-
},
22-
hasHydrated: false,
23-
setHasHydrated: (value) => set({ hasHydrated: value }),
24-
toggleAds: () =>
25-
set({
26-
settings: {
27-
...get().settings,
28-
adsDisabled: !get().settings.adsDisabled,
29-
},
30-
}),
31-
}),
32-
{
33-
name: 'user_settings_v1',
34-
storage: createJSONStorage(() => localStorage),
35-
onRehydrateStorage: () => (state, error) => {
36-
if (!state || error) return
37-
state.setHasHydrated(true)
38-
},
39-
partialize: (state) => ({ settings: state.settings }),
40-
}
41-
)
42-
)
43-
44-
export function useAdsPreference() {
45-
const userQuery = useCurrentUserQuery()
46-
const { settings } = useUserSettingsStore((s) => ({
47-
settings: s.settings,
48-
}))
22+
export const useUserSettingsStore = create<UserSettingsState>()((set, get) => ({
23+
settings: {
24+
// Remove adsDisabled initialization
25+
},
26+
hasHydrated: true, // No need for hydration since we're not using persistence
27+
setHasHydrated: (value) => set({ hasHydrated: value }),
28+
// Remove toggleAds function
29+
}))
4930

50-
const adsEnabled = userQuery.data
51-
? userQuery.data.capabilities.includes('disableAds') &&
52-
!settings.adsDisabled
53-
: true
54-
return { adsEnabled }
55-
}
31+
// Remove the persist wrapper and localStorage configuration
32+
// The useAdsPreference function is now exported from useAdPreference.ts

0 commit comments

Comments
 (0)