@webinex/chatify 1.0.0-build2 → 1.0.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +90 -90
  2. package/src/ChatifyContext.tsx +41 -41
  3. package/src/core/action.tsx +319 -319
  4. package/src/core/client.ts +137 -137
  5. package/src/core/index.ts +14 -14
  6. package/src/core/models.tsx +75 -75
  7. package/src/core/reducer.tsx +86 -86
  8. package/src/core/state.ts +38 -38
  9. package/src/core/useAccounts.tsx +11 -11
  10. package/src/core/useAddChat.tsx +12 -12
  11. package/src/core/useAddMember.tsx +12 -12
  12. package/src/core/useAddMessage.tsx +40 -40
  13. package/src/core/useChat.tsx +12 -12
  14. package/src/core/useChatList.tsx +10 -10
  15. package/src/core/useLoadMore.tsx +42 -42
  16. package/src/core/useMessages.tsx +23 -23
  17. package/src/core/useMutation.tsx +48 -48
  18. package/src/core/useQuery.tsx +45 -45
  19. package/src/core/useReadMonitor.tsx +31 -31
  20. package/src/core/useRemoveMember.tsx +12 -12
  21. package/src/core/useSignalRMonitor.tsx +52 -52
  22. package/src/index.ts +3 -3
  23. package/src/ui/AddChat.tsx +55 -55
  24. package/src/ui/AddChatButton.tsx +19 -19
  25. package/src/ui/Aside.tsx +41 -41
  26. package/src/ui/Avatar.tsx +18 -18
  27. package/src/ui/Chat.tsx +26 -26
  28. package/src/ui/ChatBody.tsx +43 -43
  29. package/src/ui/ChatHeader.tsx +33 -33
  30. package/src/ui/ChatHeaderMembers.tsx +30 -30
  31. package/src/ui/ChatHeaderSettingsButton.tsx +26 -26
  32. package/src/ui/ChatListItem.tsx +101 -101
  33. package/src/ui/ChatName.tsx +15 -15
  34. package/src/ui/ChatSettings.tsx +59 -59
  35. package/src/ui/Chatify.tsx +106 -106
  36. package/src/ui/Footer.tsx +7 -7
  37. package/src/ui/Header.tsx +14 -14
  38. package/src/ui/Icon.tsx +21 -21
  39. package/src/ui/InputForm.tsx +51 -51
  40. package/src/ui/LoadMore.tsx +40 -40
  41. package/src/ui/Main.tsx +16 -16
  42. package/src/ui/Message.tsx +118 -118
  43. package/src/ui/MessageSkeleton.tsx +19 -19
  44. package/src/ui/SendingMessage.tsx +50 -50
  45. package/src/ui/SystemMessage.tsx +32 -32
  46. package/src/ui/index.ts +11 -11
  47. package/src/ui/localizer.tsx +68 -68
  48. package/src/ui/styles/aside.scss +94 -94
  49. package/src/ui/styles/index.scss +248 -248
  50. package/src/ui/styles/keyframes.scss +14 -14
  51. package/src/ui/styles/mixins.scss +20 -20
  52. package/src/ui/styles/variables.scss +10 -10
  53. package/src/util/customize.tsx +45 -45
  54. package/src/util/index.ts +3 -3
  55. package/src/util/uniqBy.tsx +17 -17
  56. package/src/util/uniqId.tsx +3 -3
@@ -1,319 +1,319 @@
1
- import type { MutationState, QueryState, State } from './state';
2
- import { Account, ChatListItem, Message, ReadEvent } from './models';
3
- import { MessageState } from './useMessages';
4
-
5
- function updateQuery<TData>(
6
- state: State,
7
- key: string,
8
- mutation: (queryState: QueryState<TData> | undefined) => QueryState<TData> | undefined,
9
- ) {
10
- state = {
11
- ...state,
12
- query: {
13
- ...state.query,
14
- },
15
- };
16
-
17
- const result = mutation(state.query[key]);
18
- if (result) {
19
- state.query[key] = result;
20
- }
21
- return state;
22
- }
23
-
24
- function updateMutation(
25
- state: State,
26
- key: string,
27
- mutation: (mutationState: MutationState | undefined) => MutationState | undefined,
28
- ) {
29
- state = {
30
- ...state,
31
- mutation: {
32
- ...state.mutation,
33
- },
34
- };
35
-
36
- const result = mutation(state.mutation[key]);
37
- if (result) {
38
- state.mutation[key] = result;
39
- }
40
- return state;
41
- }
42
-
43
- export const ACTIONS = {
44
- query_fetch: (state: State, { key }: { key: string }): State => {
45
- return updateQuery(state, key, (x) => ({ ...x, isFetching: true, uninitialized: false }));
46
- },
47
-
48
- query_resolve: (state: State, { key, data }: { key: string; data: any[] }): State => {
49
- return updateQuery(state, key, (x) => ({ ...x!, isFetching: false, error: undefined, data }));
50
- },
51
-
52
- query_reject: (state: State, { key, error }: { key: string; error: any }): State => {
53
- return updateQuery(state, key, (x) => ({ ...x!, isFetching: false, error }));
54
- },
55
-
56
- mutation_fetch: (state: State, { key }: { key: string }): State => {
57
- return updateMutation(state, key, (x) => ({ ...x, isFetching: true }));
58
- },
59
-
60
- mutation_resolve: (state: State, { key }: { key: string }): State => {
61
- return updateMutation(state, key, (x) => ({ ...x, isFetching: false, error: undefined }));
62
- },
63
-
64
- mutation_reject: (state: State, { key, error }: { key: string; error: any }): State => {
65
- return updateMutation(state, key, (x) => ({ ...x, isFetching: false, error }));
66
- },
67
-
68
- update: (state: State, { mutation }: { mutation: (state: State) => State }): State => {
69
- return mutation(state);
70
- },
71
-
72
- chat_open: (state: State, { chatId }: { chatId: string }): State => {
73
- return { ...state, ui: { ...state.ui, view: 'chat', chatId } };
74
- },
75
-
76
- new_chat_open: (state: State): State => {
77
- return { ...state, ui: { ...state.ui, view: 'new-chat', chatId: undefined } };
78
- },
79
-
80
- chat_settings_open: (state: State, { chatId }: { chatId: string }): State => {
81
- return { ...state, ui: { ...state.ui, view: 'chat-settings', chatId } };
82
- },
83
-
84
- message_received: (
85
- state: State,
86
- { message, requestId }: { message: Message; requestId: string },
87
- ): State => {
88
- if (state.query['messages.' + message.chatId]?.data !== undefined) {
89
- state = {
90
- ...state,
91
- query: {
92
- ...state.query,
93
- ['messages.' + message.chatId]: {
94
- ...state.query['messages.' + message.chatId],
95
- data: [{ type: 'message', ...message }, ...state.query['messages.' + message.chatId].data].filter(
96
- (x: MessageState) => x.type !== 'sending' || x.requestId !== requestId,
97
- ),
98
- },
99
- },
100
- };
101
- }
102
-
103
- if (state.query['chat-list'].data !== undefined) {
104
- state = {
105
- ...state,
106
- query: {
107
- ...state.query,
108
- 'chat-list': {
109
- ...state.query['chat-list'],
110
- data: state.query['chat-list'].data.map((x: ChatListItem) =>
111
- x.id === message.chatId
112
- ? { ...x, message, unreadCount: message.read ? x.unreadCount : x.unreadCount + 1 }
113
- : x,
114
- ),
115
- },
116
- },
117
- };
118
- }
119
-
120
- return state;
121
- },
122
-
123
- chat_created: (state: State, { chat }: { chat: ChatListItem; requestId?: string }): State => {
124
- if (state.query['chat-list']?.data === undefined) {
125
- return state;
126
- }
127
-
128
- return {
129
- ...state,
130
- query: {
131
- ...state.query,
132
- 'chat-list': {
133
- ...state.query['chat-list'],
134
- data: [chat, ...state.query['chat-list'].data!],
135
- },
136
- },
137
- };
138
- },
139
-
140
- read: (state: State, { id, timestamp }: { id: string; timestamp: number }): State => {
141
- return {
142
- ...state,
143
- read: {
144
- ...state.read,
145
- queued: [...state.read.queued, id],
146
- timestamp,
147
- },
148
- };
149
- },
150
-
151
- read_send: (state: State, { ids }: { ids: string[] }): State => {
152
- return {
153
- ...state,
154
- read: {
155
- ...state.read,
156
- queued: state.read.queued.filter((x) => !ids.includes(x)),
157
- sent: [...state.read.sent, ...ids],
158
- },
159
- };
160
- },
161
-
162
- read_reject: (state: State, { ids, timestamp }: { ids: string[]; timestamp: number }): State => {
163
- return {
164
- ...state,
165
- read: {
166
- ...state.read,
167
- queued: [...state.read.queued, ...ids],
168
- sent: state.read.sent.filter((x) => !ids.includes(x)),
169
- timestamp,
170
- },
171
- };
172
- },
173
-
174
- read_received: (state: State, { events }: { events: ReadEvent[] }): State => {
175
- const ids = events.map((x) => x.messageId);
176
-
177
- state = {
178
- ...state,
179
- query: {
180
- ...state.query,
181
- },
182
- read: {
183
- ...state.read,
184
- sent: state.read.sent.filter((x) => !ids.includes(x)),
185
- },
186
- };
187
-
188
- Object.keys(state.query)
189
- .filter((x) => x.startsWith('messages.'))
190
- .forEach((key) => {
191
- state.query[key] = { ...state.query[key] };
192
- const queryState: QueryState<MessageState[]> = state.query[key];
193
-
194
- queryState.data = queryState.data?.map((x) => {
195
- if (x.type === 'message' && ids.includes(x.id)) {
196
- return { ...x, read: true };
197
- }
198
-
199
- return x;
200
- });
201
- });
202
-
203
- state.query['chat-list'] = { ...state.query['chat-list'] };
204
- const chatListState: QueryState<ChatListItem[]> = state.query['chat-list'];
205
- chatListState.data = chatListState.data?.map((x) =>
206
- ids.includes(x.message.id) ? { ...x, message: { ...x.message, read: true } } : x,
207
- );
208
-
209
- chatListState.data = chatListState.data?.map((chat) => ({
210
- ...chat,
211
- unreadCount: chat.unreadCount - events.filter((x) => x.chatId === chat.id).length,
212
- }));
213
-
214
- return state;
215
- },
216
-
217
- member_added: (
218
- state: State,
219
- {
220
- chatId,
221
- chatName,
222
- account,
223
- me,
224
- message,
225
- }: { chatId: string; chatName: string; account: Account; me: string; message: Message },
226
- ): State => {
227
- state = {
228
- ...state,
229
- query: {
230
- ...state.query,
231
- },
232
- };
233
-
234
- if (state.query['chat.' + chatId]?.data !== undefined) {
235
- state.query['chat.' + chatId] = {
236
- ...state.query['chat.' + chatId],
237
- data: { ...state.query['chat.' + chatId].data },
238
- };
239
- state.query['chat.' + chatId].data.members = [...state.query['chat.' + chatId].data.members, account];
240
- }
241
-
242
- if (state.query['messages.' + chatId]?.data !== undefined) {
243
- state.query['messages.' + chatId] = {
244
- ...state.query['messages.' + chatId],
245
- data: [{ type: 'message', ...message }, ...state.query['messages.' + chatId].data],
246
- };
247
- }
248
-
249
- if (account.id !== me && state.query['chat-list'].data !== undefined) {
250
- state.query['chat-list'] = { ...state.query['chat-list'] };
251
- state.query['chat-list'].data = state.query['chat-list'].data.map((x: ChatListItem) =>
252
- x.id === chatId ? { ...x, message: { ...message } } : x,
253
- );
254
- }
255
-
256
- if (account.id === me && state.query['chat-list'].data !== undefined) {
257
- const listItem: ChatListItem = {
258
- id: chatId,
259
- name: chatName,
260
- message: { ...message },
261
- unreadCount: 0,
262
- };
263
-
264
- state.query['chat-list'] = {
265
- ...state.query['chat-list'],
266
- data: [...state.query['chat-list'].data, listItem],
267
- };
268
- }
269
-
270
- return state;
271
- },
272
-
273
- member_removed: (
274
- state: State,
275
- {
276
- chatId,
277
- accountId,
278
- deleteHistory,
279
- me,
280
- }: { chatId: string; accountId: string; deleteHistory: boolean; me: string },
281
- ): State => {
282
- state = {
283
- ...state,
284
- query: {
285
- ...state.query,
286
- },
287
- };
288
-
289
- if (state.query['chat.' + chatId]?.data !== undefined) {
290
- state.query['chat.' + chatId] = {
291
- ...state.query['chat.' + chatId],
292
- data: { ...state.query['chat.' + chatId].data },
293
- };
294
- state.query['chat.' + chatId].data.members = state.query['chat.' + chatId].data.members.filter(
295
- (x: Account) => x.id !== accountId,
296
- );
297
- }
298
-
299
- if (accountId === me && deleteHistory && state.query['chat-list']?.data) {
300
- state.query['chat-list'] = { ...state.query['chat-list'] };
301
- state.query['chat-list'].data = state.query['chat-list'].data.filter(
302
- (x: ChatListItem) => x.id !== chatId,
303
- );
304
- }
305
-
306
- if (accountId === me && deleteHistory && state.query['chat-list']?.data && state.ui.chatId === chatId) {
307
- state.ui = { ...state.ui, chatId: state.query['chat-list']?.data.at(0)?.id };
308
- }
309
-
310
- return state;
311
- },
312
- };
313
-
314
- export type Action = {
315
- [K in keyof typeof ACTIONS]: {
316
- type: K;
317
- data: Parameters<(typeof ACTIONS)[K]>[1];
318
- };
319
- }[keyof typeof ACTIONS];
1
+ import type { MutationState, QueryState, State } from './state';
2
+ import { Account, ChatListItem, Message, ReadEvent } from './models';
3
+ import { MessageState } from './useMessages';
4
+
5
+ function updateQuery<TData>(
6
+ state: State,
7
+ key: string,
8
+ mutation: (queryState: QueryState<TData> | undefined) => QueryState<TData> | undefined,
9
+ ) {
10
+ state = {
11
+ ...state,
12
+ query: {
13
+ ...state.query,
14
+ },
15
+ };
16
+
17
+ const result = mutation(state.query[key]);
18
+ if (result) {
19
+ state.query[key] = result;
20
+ }
21
+ return state;
22
+ }
23
+
24
+ function updateMutation(
25
+ state: State,
26
+ key: string,
27
+ mutation: (mutationState: MutationState | undefined) => MutationState | undefined,
28
+ ) {
29
+ state = {
30
+ ...state,
31
+ mutation: {
32
+ ...state.mutation,
33
+ },
34
+ };
35
+
36
+ const result = mutation(state.mutation[key]);
37
+ if (result) {
38
+ state.mutation[key] = result;
39
+ }
40
+ return state;
41
+ }
42
+
43
+ export const ACTIONS = {
44
+ query_fetch: (state: State, { key }: { key: string }): State => {
45
+ return updateQuery(state, key, (x) => ({ ...x, isFetching: true, uninitialized: false }));
46
+ },
47
+
48
+ query_resolve: (state: State, { key, data }: { key: string; data: any[] }): State => {
49
+ return updateQuery(state, key, (x) => ({ ...x!, isFetching: false, error: undefined, data }));
50
+ },
51
+
52
+ query_reject: (state: State, { key, error }: { key: string; error: any }): State => {
53
+ return updateQuery(state, key, (x) => ({ ...x!, isFetching: false, error }));
54
+ },
55
+
56
+ mutation_fetch: (state: State, { key }: { key: string }): State => {
57
+ return updateMutation(state, key, (x) => ({ ...x, isFetching: true }));
58
+ },
59
+
60
+ mutation_resolve: (state: State, { key }: { key: string }): State => {
61
+ return updateMutation(state, key, (x) => ({ ...x, isFetching: false, error: undefined }));
62
+ },
63
+
64
+ mutation_reject: (state: State, { key, error }: { key: string; error: any }): State => {
65
+ return updateMutation(state, key, (x) => ({ ...x, isFetching: false, error }));
66
+ },
67
+
68
+ update: (state: State, { mutation }: { mutation: (state: State) => State }): State => {
69
+ return mutation(state);
70
+ },
71
+
72
+ chat_open: (state: State, { chatId }: { chatId: string }): State => {
73
+ return { ...state, ui: { ...state.ui, view: 'chat', chatId } };
74
+ },
75
+
76
+ new_chat_open: (state: State): State => {
77
+ return { ...state, ui: { ...state.ui, view: 'new-chat', chatId: undefined } };
78
+ },
79
+
80
+ chat_settings_open: (state: State, { chatId }: { chatId: string }): State => {
81
+ return { ...state, ui: { ...state.ui, view: 'chat-settings', chatId } };
82
+ },
83
+
84
+ message_received: (
85
+ state: State,
86
+ { message, requestId }: { message: Message; requestId: string },
87
+ ): State => {
88
+ if (state.query['messages.' + message.chatId]?.data !== undefined) {
89
+ state = {
90
+ ...state,
91
+ query: {
92
+ ...state.query,
93
+ ['messages.' + message.chatId]: {
94
+ ...state.query['messages.' + message.chatId],
95
+ data: [{ type: 'message', ...message }, ...state.query['messages.' + message.chatId].data].filter(
96
+ (x: MessageState) => x.type !== 'sending' || x.requestId !== requestId,
97
+ ),
98
+ },
99
+ },
100
+ };
101
+ }
102
+
103
+ if (state.query['chat-list'].data !== undefined) {
104
+ state = {
105
+ ...state,
106
+ query: {
107
+ ...state.query,
108
+ 'chat-list': {
109
+ ...state.query['chat-list'],
110
+ data: state.query['chat-list'].data.map((x: ChatListItem) =>
111
+ x.id === message.chatId
112
+ ? { ...x, message, unreadCount: message.read ? x.unreadCount : x.unreadCount + 1 }
113
+ : x,
114
+ ),
115
+ },
116
+ },
117
+ };
118
+ }
119
+
120
+ return state;
121
+ },
122
+
123
+ chat_created: (state: State, { chat }: { chat: ChatListItem; requestId?: string }): State => {
124
+ if (state.query['chat-list']?.data === undefined) {
125
+ return state;
126
+ }
127
+
128
+ return {
129
+ ...state,
130
+ query: {
131
+ ...state.query,
132
+ 'chat-list': {
133
+ ...state.query['chat-list'],
134
+ data: [chat, ...state.query['chat-list'].data!],
135
+ },
136
+ },
137
+ };
138
+ },
139
+
140
+ read: (state: State, { id, timestamp }: { id: string; timestamp: number }): State => {
141
+ return {
142
+ ...state,
143
+ read: {
144
+ ...state.read,
145
+ queued: [...state.read.queued, id],
146
+ timestamp,
147
+ },
148
+ };
149
+ },
150
+
151
+ read_send: (state: State, { ids }: { ids: string[] }): State => {
152
+ return {
153
+ ...state,
154
+ read: {
155
+ ...state.read,
156
+ queued: state.read.queued.filter((x) => !ids.includes(x)),
157
+ sent: [...state.read.sent, ...ids],
158
+ },
159
+ };
160
+ },
161
+
162
+ read_reject: (state: State, { ids, timestamp }: { ids: string[]; timestamp: number }): State => {
163
+ return {
164
+ ...state,
165
+ read: {
166
+ ...state.read,
167
+ queued: [...state.read.queued, ...ids],
168
+ sent: state.read.sent.filter((x) => !ids.includes(x)),
169
+ timestamp,
170
+ },
171
+ };
172
+ },
173
+
174
+ read_received: (state: State, { events }: { events: ReadEvent[] }): State => {
175
+ const ids = events.map((x) => x.messageId);
176
+
177
+ state = {
178
+ ...state,
179
+ query: {
180
+ ...state.query,
181
+ },
182
+ read: {
183
+ ...state.read,
184
+ sent: state.read.sent.filter((x) => !ids.includes(x)),
185
+ },
186
+ };
187
+
188
+ Object.keys(state.query)
189
+ .filter((x) => x.startsWith('messages.'))
190
+ .forEach((key) => {
191
+ state.query[key] = { ...state.query[key] };
192
+ const queryState: QueryState<MessageState[]> = state.query[key];
193
+
194
+ queryState.data = queryState.data?.map((x) => {
195
+ if (x.type === 'message' && ids.includes(x.id)) {
196
+ return { ...x, read: true };
197
+ }
198
+
199
+ return x;
200
+ });
201
+ });
202
+
203
+ state.query['chat-list'] = { ...state.query['chat-list'] };
204
+ const chatListState: QueryState<ChatListItem[]> = state.query['chat-list'];
205
+ chatListState.data = chatListState.data?.map((x) =>
206
+ ids.includes(x.message.id) ? { ...x, message: { ...x.message, read: true } } : x,
207
+ );
208
+
209
+ chatListState.data = chatListState.data?.map((chat) => ({
210
+ ...chat,
211
+ unreadCount: chat.unreadCount - events.filter((x) => x.chatId === chat.id).length,
212
+ }));
213
+
214
+ return state;
215
+ },
216
+
217
+ member_added: (
218
+ state: State,
219
+ {
220
+ chatId,
221
+ chatName,
222
+ account,
223
+ me,
224
+ message,
225
+ }: { chatId: string; chatName: string; account: Account; me: string; message: Message },
226
+ ): State => {
227
+ state = {
228
+ ...state,
229
+ query: {
230
+ ...state.query,
231
+ },
232
+ };
233
+
234
+ if (state.query['chat.' + chatId]?.data !== undefined) {
235
+ state.query['chat.' + chatId] = {
236
+ ...state.query['chat.' + chatId],
237
+ data: { ...state.query['chat.' + chatId].data },
238
+ };
239
+ state.query['chat.' + chatId].data.members = [...state.query['chat.' + chatId].data.members, account];
240
+ }
241
+
242
+ if (state.query['messages.' + chatId]?.data !== undefined) {
243
+ state.query['messages.' + chatId] = {
244
+ ...state.query['messages.' + chatId],
245
+ data: [{ type: 'message', ...message }, ...state.query['messages.' + chatId].data],
246
+ };
247
+ }
248
+
249
+ if (account.id !== me && state.query['chat-list'].data !== undefined) {
250
+ state.query['chat-list'] = { ...state.query['chat-list'] };
251
+ state.query['chat-list'].data = state.query['chat-list'].data.map((x: ChatListItem) =>
252
+ x.id === chatId ? { ...x, message: { ...message } } : x,
253
+ );
254
+ }
255
+
256
+ if (account.id === me && state.query['chat-list'].data !== undefined) {
257
+ const listItem: ChatListItem = {
258
+ id: chatId,
259
+ name: chatName,
260
+ message: { ...message },
261
+ unreadCount: 0,
262
+ };
263
+
264
+ state.query['chat-list'] = {
265
+ ...state.query['chat-list'],
266
+ data: [...state.query['chat-list'].data, listItem],
267
+ };
268
+ }
269
+
270
+ return state;
271
+ },
272
+
273
+ member_removed: (
274
+ state: State,
275
+ {
276
+ chatId,
277
+ accountId,
278
+ deleteHistory,
279
+ me,
280
+ }: { chatId: string; accountId: string; deleteHistory: boolean; me: string },
281
+ ): State => {
282
+ state = {
283
+ ...state,
284
+ query: {
285
+ ...state.query,
286
+ },
287
+ };
288
+
289
+ if (state.query['chat.' + chatId]?.data !== undefined) {
290
+ state.query['chat.' + chatId] = {
291
+ ...state.query['chat.' + chatId],
292
+ data: { ...state.query['chat.' + chatId].data },
293
+ };
294
+ state.query['chat.' + chatId].data.members = state.query['chat.' + chatId].data.members.filter(
295
+ (x: Account) => x.id !== accountId,
296
+ );
297
+ }
298
+
299
+ if (accountId === me && deleteHistory && state.query['chat-list']?.data) {
300
+ state.query['chat-list'] = { ...state.query['chat-list'] };
301
+ state.query['chat-list'].data = state.query['chat-list'].data.filter(
302
+ (x: ChatListItem) => x.id !== chatId,
303
+ );
304
+ }
305
+
306
+ if (accountId === me && deleteHistory && state.query['chat-list']?.data && state.ui.chatId === chatId) {
307
+ state.ui = { ...state.ui, chatId: state.query['chat-list']?.data.at(0)?.id };
308
+ }
309
+
310
+ return state;
311
+ },
312
+ };
313
+
314
+ export type Action = {
315
+ [K in keyof typeof ACTIONS]: {
316
+ type: K;
317
+ data: Parameters<(typeof ACTIONS)[K]>[1];
318
+ };
319
+ }[keyof typeof ACTIONS];