From e05f5445e767dd3a1b2489066061a838b5484eb4 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 19:10:48 +0200 Subject: [PATCH] feat(P3.T5): useOwnDevices hook with realtime refresh Adds useOwnDevices React hook that fetches the user device list via listOwnDevices and re-fetches on any postgres_changes event on the devices table, matching the channel/filter convention in AuthContext. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/desktop/src/hooks/useOwnDevices.ts | 74 +++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 apps/desktop/src/hooks/useOwnDevices.ts diff --git a/apps/desktop/src/hooks/useOwnDevices.ts b/apps/desktop/src/hooks/useOwnDevices.ts new file mode 100644 index 0000000..566b6ab --- /dev/null +++ b/apps/desktop/src/hooks/useOwnDevices.ts @@ -0,0 +1,74 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { type DeviceRecord, listOwnDevices, revokeDevice } from '@chat-app/shared/auth'; + +import { useAuth } from '../context/AuthContext'; +import { supabase } from '../lib/supabase'; + +interface State { + devices: DeviceRecord[]; + loading: boolean; + error: string | null; +} + +export function useOwnDevices(): { + devices: DeviceRecord[]; + loading: boolean; + error: string | null; + refresh: () => Promise; + revoke: (deviceId: string) => Promise; +} { + const { session } = useAuth(); + const userId = session?.user.id ?? null; + const [state, setState] = useState({ devices: [], loading: true, error: null }); + + const refresh = useCallback(async () => { + try { + setState((s) => ({ ...s, loading: true, error: null })); + const list = await listOwnDevices(supabase); + setState({ devices: list, loading: false, error: null }); + } catch (err) { + setState((s) => ({ + ...s, + loading: false, + error: err instanceof Error ? err.message : 'failed to load devices', + })); + } + }, []); + + useEffect(() => { + if (!userId) { + setState({ devices: [], loading: false, error: null }); + return; + } + void refresh(); + const channel = supabase + .channel('devices:list:' + userId) + .on( + 'postgres_changes', + { + event: '*', + schema: 'public', + table: 'devices', + filter: 'user_id=eq.' + userId, + }, + () => { + void refresh(); + }, + ) + .subscribe(); + return () => { + void supabase.removeChannel(channel); + }; + }, [userId, refresh]); + + const revoke = useCallback( + async (deviceId: string) => { + await revokeDevice(supabase, deviceId); + await refresh(); + }, + [refresh], + ); + + return { devices: state.devices, loading: state.loading, error: state.error, refresh, revoke }; +}