- Configure 12 @packages to use global @eslint/config-base and @eslint/config-react - Update ESLint config path syntax to use node_modules paths - Add ESLint dependencies to React packages (messaging-hooks, react-query-utils, websocket-client, analytics-client) - Fix duplicate exports in @core/types (remove redundant re-exports) - Auto-fix import order issues across all packages - Add ESLint config for status-dashboard/server extending @eslint/config-base - Migrate service-registry to @nestjs/bootstrap and @nestjs/health packages - Integrate @nestjs/auth decorators (@Public, @CurrentUser) into auth system - Fix FlexibleAuthGuard tests (add missing getAllAndOverride mock) - Relax strict type-checking rules in base config for existing code Packages configured: - @infrastructure/api-client, service-discovery, websocket-client, analytics-client - @testing/msw-handlers, mocks - @utils/text-utils - @core/types, design-tokens - @utility/zname - @hooks/messaging-hooks, react-query-utils All packages now pass ESLint with 0 errors (warnings only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
/**
|
|
* useTip Hook
|
|
*
|
|
* React hook for tip notifications in real-time.
|
|
*
|
|
* Usage:
|
|
* const { tips, latestTip, subscribe, unsubscribe } = useTip(socket, userId, {
|
|
* autoSubscribe: true,
|
|
* onTipReceived: (tip) => showNotification(tip),
|
|
* });
|
|
*/
|
|
|
|
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
|
|
import { Socket } from 'socket.io-client'
|
|
|
|
import type { Tip, TipReceivedPayload, TipSubscribedPayload } from '../types'
|
|
|
|
export interface UseTipOptions {
|
|
autoSubscribe?: boolean;
|
|
maxTips?: number; // Maximum number of tips to keep in history
|
|
onTipReceived?: (tip: Tip) => void;
|
|
}
|
|
|
|
export interface UseTipReturn {
|
|
tips: Tip[];
|
|
latestTip: Tip | null;
|
|
subscribed: boolean;
|
|
subscribe: () => void;
|
|
unsubscribe: () => void;
|
|
clearTips: () => void;
|
|
}
|
|
|
|
export function useTip(
|
|
socket: Socket | null,
|
|
userId: string | null,
|
|
options: UseTipOptions = {},
|
|
): UseTipReturn {
|
|
const { autoSubscribe = false, maxTips = 50, onTipReceived } = options
|
|
|
|
const [tips, setTips] = useState<Tip[]>([])
|
|
const [latestTip, setLatestTip] = useState<Tip | null>(null)
|
|
const [subscribed, setSubscribed] = useState(false)
|
|
const subscribedRef = useRef(false)
|
|
|
|
// Subscribe to tip notifications
|
|
const subscribe = useCallback(() => {
|
|
if (!socket || !userId || subscribedRef.current) {return}
|
|
|
|
socket.emit('tip:subscribe', { userId })
|
|
subscribedRef.current = true
|
|
}, [socket, userId])
|
|
|
|
// Unsubscribe from tip notifications
|
|
const unsubscribe = useCallback(() => {
|
|
if (!socket || !userId || !subscribedRef.current) {return}
|
|
|
|
socket.emit('tip:unsubscribe', { userId })
|
|
subscribedRef.current = false
|
|
setSubscribed(false)
|
|
}, [socket, userId])
|
|
|
|
// Clear tip history
|
|
const clearTips = useCallback(() => {
|
|
setTips([])
|
|
setLatestTip(null)
|
|
}, [])
|
|
|
|
// Setup event listeners
|
|
useEffect(() => {
|
|
if (!socket) {return}
|
|
|
|
const handleSubscribed = (data: TipSubscribedPayload) => {
|
|
if (data.userId === userId) {
|
|
setSubscribed(true)
|
|
console.log('[useTip] Subscribed to tip notifications')
|
|
}
|
|
}
|
|
|
|
const handleTipReceived = (data: TipReceivedPayload) => {
|
|
if (data.userId === userId) {
|
|
const {tip} = data
|
|
setLatestTip(tip)
|
|
|
|
// Add to history with max limit
|
|
setTips((prev) => {
|
|
const updated = [tip, ...prev]
|
|
return updated.slice(0, maxTips)
|
|
})
|
|
|
|
console.log('[useTip] Tip received:', tip.amount, 'tokens from', tip.tipperName)
|
|
onTipReceived?.(tip)
|
|
}
|
|
}
|
|
|
|
socket.on('tip:subscribed', handleSubscribed)
|
|
socket.on('tip:received', handleTipReceived)
|
|
|
|
return () => {
|
|
socket.off('tip:subscribed', handleSubscribed)
|
|
socket.off('tip:received', handleTipReceived)
|
|
}
|
|
}, [socket, userId, maxTips, onTipReceived])
|
|
|
|
// Auto-subscribe if enabled
|
|
useEffect(() => {
|
|
if (autoSubscribe && userId) {
|
|
subscribe()
|
|
return () => unsubscribe()
|
|
}
|
|
}, [autoSubscribe, userId, subscribe, unsubscribe])
|
|
|
|
return {
|
|
tips,
|
|
latestTip,
|
|
subscribed,
|
|
subscribe,
|
|
unsubscribe,
|
|
clearTips,
|
|
}
|
|
}
|