Skip to content

Commit ca038d7

Browse files
committed
feat: add chat filter UI to webhook configuration
Add a multi-select chat dropdown to the webhook dialog so users can scope webhooks to specific iMessage conversations. The dropdown shows chat display names (or participant addresses for unnamed chats) and is populated from the existing get-chats IPC handler. The webhooks table shows the filter state as "All Chats" or a chat count.
1 parent db8e700 commit ca038d7

File tree

4 files changed

+71
-11
lines changed

4 files changed

+71
-11
lines changed

packages/ui/src/app/components/modals/AddWebhookDialog.tsx

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import {
1111
FormControl,
1212
FormErrorMessage,
1313
FormLabel,
14+
FormHelperText,
1415
Text
1516
} from '@chakra-ui/react';
1617
import { Select as MultiSelect } from 'chakra-react-select';
18+
import { ipcRenderer } from 'electron';
1719
import { FocusableElement } from '@chakra-ui/utils';
1820
import { webhookEventOptions } from '../../constants';
1921
import { MultiSelectValue } from '../../types';
@@ -47,6 +49,27 @@ export const AddWebhookDialog = ({
4749
const isUrlInvalid = (urlError ?? '').length > 0;
4850
const [eventsError, setEventsError] = useState('');
4951
const isEventsError = (eventsError ?? '').length > 0;
52+
const [chatOptions, setChatOptions] = useState([] as Array<MultiSelectValue>);
53+
const [selectedChats, setSelectedChats] = useState([] as Array<MultiSelectValue>);
54+
55+
useEffect(() => {
56+
ipcRenderer.invoke('get-chats').then((chats: any[]) => {
57+
chats.sort((a, b) => {
58+
const aHasName = !!a.displayName;
59+
const bHasName = !!b.displayName;
60+
if (aHasName === bHasName) return 0;
61+
return aHasName ? -1 : 1;
62+
});
63+
const options = chats.map((e: any) => {
64+
let label = e.displayName ?? '';
65+
if (label.length === 0) {
66+
label = e.participants?.map((p: any) => p.address).join(', ') ?? e.chatIdentifier ?? e.guid;
67+
}
68+
return { label, value: e.guid };
69+
});
70+
setChatOptions(options);
71+
});
72+
}, []);
5073

5174
useEffect(() => {
5275
if (!existingId) return;
@@ -58,6 +81,21 @@ export const AddWebhookDialog = ({
5881
}
5982
}, [existingId]);
6083

84+
useEffect(() => {
85+
if (!existingId) return;
86+
87+
const webhook = webhooks.find(e => e.id === existingId);
88+
if (webhook?.chatGuids && webhook.chatGuids.length > 0) {
89+
const matched = webhook.chatGuids.map(guid => {
90+
const option = chatOptions.find(o => o.value === guid);
91+
return option ?? { label: guid, value: guid };
92+
});
93+
setSelectedChats(matched);
94+
} else {
95+
setSelectedChats([]);
96+
}
97+
}, [existingId, chatOptions]);
98+
6199
return (
62100
<AlertDialog
63101
isOpen={isOpen}
@@ -104,7 +142,21 @@ export const AddWebhookDialog = ({
104142
<FormErrorMessage>{eventsError}</FormErrorMessage>
105143
) : null}
106144
</FormControl>
107-
145+
<FormControl mt={5}>
146+
<FormLabel>Chat Filter</FormLabel>
147+
<MultiSelect
148+
size='md'
149+
isMulti={true}
150+
options={chatOptions}
151+
value={selectedChats}
152+
placeholder='All Chats'
153+
onChange={(newValues) => {
154+
setSelectedChats(newValues as Array<MultiSelectValue>);
155+
}}
156+
/>
157+
<FormHelperText>Select specific chats to receive events from. Leave empty for all chats.</FormHelperText>
158+
</FormControl>
159+
108160
</AlertDialogBody>
109161

110162
<AlertDialogFooter>
@@ -113,6 +165,7 @@ export const AddWebhookDialog = ({
113165
onClick={() => {
114166
if (onCancel) onCancel();
115167
setUrl('');
168+
setSelectedChats([]);
116169
onClose();
117170
}}
118171
>
@@ -133,13 +186,16 @@ export const AddWebhookDialog = ({
133186
return;
134187
}
135188

189+
const chatGuids = selectedChats.length > 0 ? selectedChats.map(c => c.value) : undefined;
190+
136191
if (existingId) {
137-
dispatch(update({ id: existingId, url, events: selectedEvents }));
192+
dispatch(update({ id: existingId, url, events: selectedEvents, chatGuids: chatGuids ?? null }));
138193
} else {
139-
dispatch(create({ url, events: selectedEvents }));
194+
dispatch(create({ url, events: selectedEvents, chatGuids }));
140195
}
141196

142197
setUrl('');
198+
setSelectedChats([]);
143199
onClose();
144200
}}
145201
>
@@ -150,4 +206,4 @@ export const AddWebhookDialog = ({
150206
</AlertDialogOverlay>
151207
</AlertDialog>
152208
);
153-
};
209+
};

packages/ui/src/app/components/tables/WebhooksTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const WebhooksTable = ({ webhooks }: { webhooks: Array<WebhookItem> }): J
3333
<Tr>
3434
<Th>URL</Th>
3535
<Th>Event Subscriptions</Th>
36+
<Th>Chat Filter</Th>
3637
<Th isNumeric>Actions</Th>
3738
</Tr>
3839
</Thead>
@@ -41,6 +42,7 @@ export const WebhooksTable = ({ webhooks }: { webhooks: Array<WebhookItem> }): J
4142
<Tr key={item.id}>
4243
<Td>{item.url}</Td>
4344
<Td>{JSON.parse(item.events).map((e: string) => webhookEventValueToLabel(e)).join(', ')}</Td>
45+
<Td>{item.chatGuids && item.chatGuids.length > 0 ? `${item.chatGuids.length} chat(s)` : 'All Chats'}</Td>
4446
<Td isNumeric>
4547
<Stack direction="row" justifyContent="end">
4648
<Tooltip label='Edit' placement='bottom'>

packages/ui/src/app/slices/WebhooksSlice.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface WebhookItem {
88
id: number;
99
url: string;
1010
events: string;
11+
chatGuids: string[] | null;
1112
created: Date;
1213
}
1314

@@ -29,7 +30,7 @@ export const WebhooksSlice = createSlice({
2930
if (!exists) state.webhooks.push(i);
3031
}
3132
},
32-
create: (state, action: PayloadAction<{ url: string, events: Array<MultiSelectValue> }>) => {
33+
create: (state, action: PayloadAction<{ url: string, events: Array<MultiSelectValue>, chatGuids?: Array<string> }>) => {
3334
const exists = state.webhooks.find(e => e.url === action.payload.url);
3435
if (exists) {
3536
return showErrorToast({
@@ -53,7 +54,7 @@ export const WebhooksSlice = createSlice({
5354
});
5455
});
5556
},
56-
update: (state, action: PayloadAction<{ id: number, url: string, events: Array<MultiSelectValue> }>) => {
57+
update: (state, action: PayloadAction<{ id: number, url: string, events: Array<MultiSelectValue>, chatGuids?: Array<string> | null }>) => {
5758
const existingIndex = state.webhooks.findIndex(e => e.id === action.payload.id);
5859
if (existingIndex === -1) {
5960
return showErrorToast({
@@ -67,13 +68,14 @@ export const WebhooksSlice = createSlice({
6768
state.webhooks = state.webhooks.map(e => (e.id === action.payload.id) ?
6869
{ ...e, url: action.payload.url, events: JSON.stringify(action.payload.events.map(i => {
6970
return i.value;
70-
})) } : e);
71+
})), chatGuids: action.payload.chatGuids ?? null } : e);
7172

7273
// Send the update to the backend
7374
updateWebhook({
7475
id: action.payload.id,
7576
url: action.payload.url,
76-
events: action.payload.events
77+
events: action.payload.events,
78+
chatGuids: action.payload.chatGuids
7779
}).then(() => {
7880
showSuccessToast({
7981
id: 'webhooks',

packages/ui/src/app/utils/IpcUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,16 @@ export const getWebhooks = async () => {
7777
return await ipcRenderer.invoke('get-webhooks');
7878
};
7979

80-
export const createWebhook = async (payload: { url: string, events: Array<MultiSelectValue> }) => {
80+
export const createWebhook = async (payload: { url: string, events: Array<MultiSelectValue>, chatGuids?: Array<string> }) => {
8181
return await ipcRenderer.invoke('create-webhook', payload);
8282
};
8383

8484
export const deleteWebhook = async ({ url = null, id = null }: { url?: string | null, id?: number | null }) => {
8585
return await ipcRenderer.invoke('delete-webhook', { url, id });
8686
};
8787

88-
export const updateWebhook = async ({ id, url, events }: { id: number, url?: string, events?: Array<MultiSelectValue> }) => {
89-
return await ipcRenderer.invoke('update-webhook', { id, url, events });
88+
export const updateWebhook = async ({ id, url, events, chatGuids }: { id: number, url?: string, events?: Array<MultiSelectValue>, chatGuids?: Array<string> | null }) => {
89+
return await ipcRenderer.invoke('update-webhook', { id, url, events, chatGuids });
9090
};
9191

9292
export const reinstallHelperBundle = async () => {

0 commit comments

Comments
 (0)