Skip to content
Merged
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
2 changes: 1 addition & 1 deletion frontend/app/api/user-profile/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function GET(req: NextRequest) {

const { data, error } = await getAdminClient()
.from("user_profiles")
.select("wallet_address, email, notification_preferences")
.select("wallet_address, email, notification_preferences, muted_pools")
.eq("wallet_address", wallet)
.maybeSingle()

Expand Down
50 changes: 50 additions & 0 deletions frontend/components/group/GroupMuteNotificationsToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client"

import { useEffect, useMemo, useState } from "react"
import { useStellar } from "@/components/web3-provider"
import { useUserProfile } from "@/hooks/useUserProfile"
import { Button } from "@/components/ui/button"

export function GroupMuteNotificationsToggle({ poolId }: { poolId: string }) {
const { address } = useStellar()
const { profile, loading, saving, saveProfile } = useUserProfile(address || null)
const [localMuted, setLocalMuted] = useState(false)

useEffect(() => {
const muted = !!profile?.muted_pools?.includes(poolId)
setLocalMuted(muted)
}, [profile?.muted_pools, poolId])

const isMuted = useMemo(() => localMuted, [localMuted])

const toggle = async () => {
if (!address || !poolId) return
const current = profile?.muted_pools ?? []
const next = isMuted
? current.filter((id) => id !== poolId)
: Array.from(new Set([...current, poolId]))

setLocalMuted(!isMuted)
await saveProfile({ muted_pools: next })
}

return (
<div className="mb-4 p-3 rounded-lg bg-muted/30 flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium">Mute notifications for this pool</p>
<p className="text-xs text-muted-foreground">
Stops email notifications for this pool only.
</p>
</div>
<Button
type="button"
variant={isMuted ? "destructive" : "outline"}
onClick={toggle}
disabled={loading || saving || !profile}
className="shrink-0"
>
{isMuted ? "Muted" : "On"}
</Button>
</div>
)
}
7 changes: 7 additions & 0 deletions frontend/components/group/group-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import { usePoolData } from "@/lib/data-layer/PoolDataProvider"
import { useToast } from "@/hooks/use-toast"
import { useOptimisticTransactions } from "@/hooks/useOptimisticTransactions"
import { GroupMuteNotificationsToggle } from "@/components/group/GroupMuteNotificationsToggle"

interface GroupDetailsProps {
groupId: string
Expand Down Expand Up @@ -483,6 +484,12 @@ export function GroupDetails({ groupId, contractAddress }: GroupDetailsProps) {
</div>
)}

{/* Per-pool notification mute (email only) */}
<div className="mb-4">
{/* pool_id in DB is the same as groupId route param */}
<GroupMuteNotificationsToggle poolId={groupId} />
</div>

<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
{stats.map((stat, i) => (
<motion.div
Expand Down
12 changes: 10 additions & 2 deletions frontend/hooks/useUserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface UserProfile {
wallet_address: string
email: string | null
notification_preferences: NotificationPreferences
muted_pools: string[]
}

const DEFAULT_PREFS: NotificationPreferences = {
Expand All @@ -39,11 +40,13 @@ export function useUserProfile(walletAddress: string | null) {
wallet_address: walletAddress.toLowerCase(),
email: null,
notification_preferences: DEFAULT_PREFS,
muted_pools: [],
}
: null
)
return
}

setLoading(true)
const res = await fetch(
`/api/user-profile?wallet=${encodeURIComponent(walletAddress.toLowerCase())}`
Expand All @@ -54,8 +57,10 @@ export function useUserProfile(walletAddress: string | null) {
wallet_address: walletAddress.toLowerCase(),
email: null,
notification_preferences: DEFAULT_PREFS,
muted_pools: [],
}
)

setLoading(false)
}, [walletAddress])

Expand All @@ -64,12 +69,15 @@ export function useUserProfile(walletAddress: string | null) {
}, [fetchProfile])

const saveProfile = useCallback(
async (updates: Partial<Pick<UserProfile, "email" | "notification_preferences">>) => {
async (
updates: Partial<Pick<UserProfile, "email" | "notification_preferences" | "muted_pools">>
) => {
if (!walletAddress) return
if (IS_E2E) {
setProfile((prev) => (prev ? { ...prev, ...updates } : null))
setProfile((prev: UserProfile | null) => (prev ? { ...prev, ...updates } : null))
return
}

setSaving(true)
await fetch("/api/user-profile", {
method: "POST",
Expand Down
Loading
Loading