diff --git a/convex/schema.ts b/convex/schema.ts index 4af181b3b..a885fdcb7 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -31,6 +31,7 @@ const schema = defineSchema({ capabilities: v.array( v.union(...validCapabilities.map((cap) => v.literal(cap))) ), + adsDisabled: v.optional(v.boolean()), }), }) diff --git a/convex/users.ts b/convex/users.ts index eae24fff4..51f46173b 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -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: { @@ -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 } + }, +}) diff --git a/src/hooks/useAdPreference.ts b/src/hooks/useAdPreference.ts new file mode 100644 index 000000000..a14d60d19 --- /dev/null +++ b/src/hooks/useAdPreference.ts @@ -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 } +} diff --git a/src/routes/_libraries/account.tsx b/src/routes/_libraries/account.tsx index 1dbccd718..fff80be54 100644 --- a/src/routes/_libraries/account.tsx +++ b/src/routes/_libraries/account.tsx @@ -1,9 +1,9 @@ -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, @@ -11,10 +11,30 @@ export const Route = createFileRoute({ 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) => { + updateAdPreferenceMutation({ + adsDisabled: e.target.checked, + }) + } const signOut = async () => { await authClient.signOut() @@ -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" /> diff --git a/src/stores/userSettings.ts b/src/stores/userSettings.ts index d2238a997..ae5533a85 100644 --- a/src/stores/userSettings.ts +++ b/src/stores/userSettings.ts @@ -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()( - 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()((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