Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const schema = defineSchema({
capabilities: v.array(
v.union(...validCapabilities.map((cap) => v.literal(cap)))
),
adsDisabled: v.optional(v.boolean()),
}),
})

Expand Down
19 changes: 19 additions & 0 deletions convex/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { v } from 'convex/values'
import { mutation, query, QueryCtx } from './_generated/server'
import { Capability, CapabilitySchema } from './schema'
import { getCurrentUserConvex } from './auth'
import { Id } from './_generated/dataModel'

export const updateUserCapabilities = mutation({
args: {
Expand Down Expand Up @@ -80,3 +81,21 @@ async function requireCapability(ctx: QueryCtx, capability: Capability) {

return { currentUser }
}

// Toggle ad preference (only for users with disableAds capability)
export const updateAdPreference = mutation({
args: {
adsDisabled: v.boolean(),
},
handler: async (ctx, args) => {
// Validate admin capability
const { currentUser } = await requireCapability(ctx, 'disableAds')

// Update target user's capabilities
await ctx.db.patch(currentUser.userId as Id<'users'>, {
adsDisabled: args.adsDisabled,
})

return { success: true }
},
})
19 changes: 19 additions & 0 deletions src/hooks/useAdPreference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useCurrentUserQuery } from './useCurrentUser'

// Legacy hook for backward compatibility - now uses current user query
export function useAdsPreference() {
const userQuery = useCurrentUserQuery()

if (userQuery.isLoading || !userQuery.data) {
return { adsEnabled: true } // Default to showing ads while loading or not authenticated
}

const user = userQuery.data
const adsDisabled = user.adsDisabled ?? false
const canDisableAds = user.capabilities.includes('disableAds')

// Ads are enabled if user can't disable them OR if they haven't disabled them
const adsEnabled = !canDisableAds || !adsDisabled

return { adsEnabled }
}
32 changes: 26 additions & 6 deletions src/routes/_libraries/account.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import { useUserSettingsStore } from '~/stores/userSettings'
import { FaSignOutAlt } from 'react-icons/fa'
import { Authenticated, Unauthenticated } from 'convex/react'
import { Authenticated, Unauthenticated, useMutation } from 'convex/react'
import { Link, redirect } from '@tanstack/react-router'
import { authClient } from '~/utils/auth.client'
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
import { api } from 'convex/_generated/api'

export const Route = createFileRoute({
component: AccountPage,
})

function UserSettings() {
const userQuery = useCurrentUserQuery()
const adsDisabled = useUserSettingsStore((s) => s.settings.adsDisabled)
const toggleAds = useUserSettingsStore((s) => s.toggleAds)
// Use current user query directly instead of separate ad preference query
const updateAdPreferenceMutation = useMutation(
api.users.updateAdPreference
).withOptimisticUpdate((localStore, args) => {
const { adsDisabled } = args
const currentValue = localStore.getQuery(api.auth.getCurrentUser)
if (currentValue !== undefined) {
localStore.setQuery(api.auth.getCurrentUser, {}, {
...currentValue,
adsDisabled: adsDisabled,
} as any)
}
})

const canDisableAds = userQuery.data?.capabilities.includes('disableAds')
// Get values directly from the current user data
const adsDisabled = userQuery.data?.adsDisabled ?? false
const canDisableAds =
userQuery.data?.capabilities.includes('disableAds') ?? false

const handleToggleAds = (e: React.ChangeEvent<HTMLInputElement>) => {
updateAdPreferenceMutation({
adsDisabled: e.target.checked,
})
}

const signOut = async () => {
await authClient.signOut()
Expand Down Expand Up @@ -53,7 +73,7 @@ function UserSettings() {
type="checkbox"
className="h-4 w-4 accent-blue-600 my-1"
checked={adsDisabled}
onChange={toggleAds}
onChange={handleToggleAds}
disabled={userQuery.isLoading}
aria-label="Disable Ads"
/>
Expand Down
63 changes: 20 additions & 43 deletions src/stores/userSettings.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,32 @@
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
// Remove persist and createJSONStorage imports since we no longer use localStorage
// import { persist, createJSONStorage } from 'zustand/middleware'
// Remove the useCurrentUserQuery import since we'll use the new hook
// import { useCurrentUserQuery } from '~/hooks/useCurrentUser'

// Update the export to use the new hook
export { useAdsPreference } from '~/hooks/useAdPreference'

export type UserSettings = {
adsDisabled: boolean
// Remove adsDisabled since it's now handled by Convex
// Other settings can be added here in the future
}

type UserSettingsState = {
settings: UserSettings
hasHydrated: boolean
setHasHydrated: (value: boolean) => void
toggleAds: () => void
// Remove toggleAds since it's now handled by the new hooks
}

export const useUserSettingsStore = create<UserSettingsState>()(
persist(
(set, get) => ({
settings: {
adsDisabled: false,
},
hasHydrated: false,
setHasHydrated: (value) => set({ hasHydrated: value }),
toggleAds: () =>
set({
settings: {
...get().settings,
adsDisabled: !get().settings.adsDisabled,
},
}),
}),
{
name: 'user_settings_v1',
storage: createJSONStorage(() => localStorage),
onRehydrateStorage: () => (state, error) => {
if (!state || error) return
state.setHasHydrated(true)
},
partialize: (state) => ({ settings: state.settings }),
}
)
)

export function useAdsPreference() {
const userQuery = useCurrentUserQuery()
const { settings } = useUserSettingsStore((s) => ({
settings: s.settings,
}))
export const useUserSettingsStore = create<UserSettingsState>()((set, get) => ({
settings: {
// Remove adsDisabled initialization
},
hasHydrated: true, // No need for hydration since we're not using persistence
setHasHydrated: (value) => set({ hasHydrated: value }),
// Remove toggleAds function
}))

const adsEnabled = userQuery.data
? userQuery.data.capabilities.includes('disableAds') &&
!settings.adsDisabled
: true
return { adsEnabled }
}
// Remove the persist wrapper and localStorage configuration
// The useAdsPreference function is now exported from useAdPreference.ts
Loading