@startsimpli/hooks 0.4.6 → 0.4.7
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.
- package/package.json +7 -2
- package/src/index.ts +27 -0
- package/src/useCSV.ts +10 -10
- package/src/useEnrichment.ts +156 -0
- package/src/useMessageTemplates.ts +67 -0
- package/src/useMessages.ts +86 -0
- package/src/useTargetLists.ts +132 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/hooks",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Shared React hooks for StartSimpli apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -24,14 +24,19 @@
|
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"react": ">=18.0.0",
|
|
27
|
-
"@tanstack/react-query": ">=5.0.0"
|
|
27
|
+
"@tanstack/react-query": ">=5.0.0",
|
|
28
|
+
"@startsimpli/api": "workspace:*"
|
|
28
29
|
},
|
|
29
30
|
"peerDependenciesMeta": {
|
|
30
31
|
"@tanstack/react-query": {
|
|
31
32
|
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"@startsimpli/api": {
|
|
35
|
+
"optional": true
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
39
|
+
"@startsimpli/api": "workspace:*",
|
|
35
40
|
"@tanstack/react-query": "^5.0.0",
|
|
36
41
|
"@testing-library/react": "^14.0.0",
|
|
37
42
|
"@types/node": "^20.11.0",
|
package/src/index.ts
CHANGED
|
@@ -22,12 +22,39 @@ export {
|
|
|
22
22
|
getFilterDescription,
|
|
23
23
|
} from './filter-encoding'
|
|
24
24
|
export { useCRUDMutation } from './useCRUDMutation'
|
|
25
|
+
export {
|
|
26
|
+
useMessages,
|
|
27
|
+
useMessage,
|
|
28
|
+
useCreateMessage,
|
|
29
|
+
useSendMessage,
|
|
30
|
+
useScheduleMessage,
|
|
31
|
+
useSendTestMessage,
|
|
32
|
+
useMessageChannels,
|
|
33
|
+
} from './useMessages'
|
|
34
|
+
export {
|
|
35
|
+
useMessageTemplates,
|
|
36
|
+
useMessageTemplate,
|
|
37
|
+
useCreateMessageTemplate,
|
|
38
|
+
useUpdateMessageTemplate,
|
|
39
|
+
useDeleteMessageTemplate,
|
|
40
|
+
} from './useMessageTemplates'
|
|
25
41
|
export { useSavedViews } from './useSavedViews'
|
|
26
42
|
export type { SavedView as SavedViewEntry } from './useSavedViews'
|
|
27
43
|
export { useRecentlyViewed } from './useRecentlyViewed'
|
|
28
44
|
export { useWizard } from './useWizard'
|
|
29
45
|
export type { WizardState } from './useWizard'
|
|
30
46
|
export { useCSVImport, useCSVExport } from './useCSV'
|
|
47
|
+
export { useTargetListDetail, useTargetListMutations, TARGET_LIST_KEYS } from './useTargetLists'
|
|
48
|
+
export type { TargetListApiFns, UseTargetListMutationsOptions } from './useTargetLists'
|
|
49
|
+
export { useEnrichment, useBatchEnrichment, useQueueStatus } from './useEnrichment'
|
|
50
|
+
export type {
|
|
51
|
+
UseEnrichmentState,
|
|
52
|
+
UseEnrichmentOptions,
|
|
53
|
+
UseBatchEnrichmentState,
|
|
54
|
+
UseBatchEnrichmentOptions,
|
|
55
|
+
UseQueueStatusState,
|
|
56
|
+
UseQueueStatusOptions,
|
|
57
|
+
} from './useEnrichment'
|
|
31
58
|
export type {
|
|
32
59
|
CSVColumnMapping,
|
|
33
60
|
CSVPreviewResult,
|
package/src/useCSV.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
export interface CSVColumnMapping {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
csvColumn: string
|
|
5
|
+
targetField: string
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface CSVPreviewResult<TField extends string = string> {
|
|
9
9
|
columns: string[]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
sampleRows: Record<string, string>[]
|
|
11
|
+
suggestedMappings?: CSVColumnMapping[]
|
|
12
|
+
totalRows?: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface CSVImportResult {
|
|
16
|
-
|
|
16
|
+
totalRows: number
|
|
17
17
|
successful: number
|
|
18
18
|
failed: number
|
|
19
19
|
errors: Array<{ row: number; message: string }>
|
|
@@ -63,7 +63,7 @@ export function useCSVImport<TField extends string = string>({
|
|
|
63
63
|
try {
|
|
64
64
|
const previewData = await previewFn(selectedFile)
|
|
65
65
|
setPreview(previewData)
|
|
66
|
-
setMappings(previewData.
|
|
66
|
+
setMappings(previewData.suggestedMappings ?? [])
|
|
67
67
|
setStep('mapping')
|
|
68
68
|
} catch {
|
|
69
69
|
setError('Failed to preview CSV file')
|
|
@@ -76,13 +76,13 @@ export function useCSVImport<TField extends string = string>({
|
|
|
76
76
|
|
|
77
77
|
const updateMapping = useCallback((csvColumn: string, targetField: string) => {
|
|
78
78
|
setMappings((prev) => {
|
|
79
|
-
const existing = prev.find((m) => m.
|
|
79
|
+
const existing = prev.find((m) => m.csvColumn === csvColumn)
|
|
80
80
|
if (existing) {
|
|
81
81
|
return prev.map((m) =>
|
|
82
|
-
m.
|
|
82
|
+
m.csvColumn === csvColumn ? { ...m, targetField: targetField } : m
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
|
-
return [...prev, {
|
|
85
|
+
return [...prev, { csvColumn: csvColumn, targetField: targetField }]
|
|
86
86
|
})
|
|
87
87
|
}, [])
|
|
88
88
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react'
|
|
2
|
+
import type { EnrichmentResult, QueueStatus } from '@startsimpli/api'
|
|
3
|
+
|
|
4
|
+
export interface EnrichFn {
|
|
5
|
+
(contactId: string): Promise<EnrichmentResult>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BulkEnrichFn {
|
|
9
|
+
(contactIds: string[], onProgress?: (completed: number, total: number) => void): Promise<EnrichmentResult[]>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface QueueStatusFn {
|
|
13
|
+
(): Promise<QueueStatus>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseEnrichmentOptions {
|
|
17
|
+
enrichFn: EnrichFn
|
|
18
|
+
onSuccess?: (result: EnrichmentResult) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseEnrichmentState {
|
|
22
|
+
isEnriching: boolean
|
|
23
|
+
result: EnrichmentResult | null
|
|
24
|
+
error: string | null
|
|
25
|
+
enrich: (contactId: string) => Promise<void>
|
|
26
|
+
reset: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useEnrichment({ enrichFn, onSuccess }: UseEnrichmentOptions): UseEnrichmentState {
|
|
30
|
+
const [isEnriching, setIsEnriching] = useState(false)
|
|
31
|
+
const [result, setResult] = useState<EnrichmentResult | null>(null)
|
|
32
|
+
const [error, setError] = useState<string | null>(null)
|
|
33
|
+
|
|
34
|
+
const enrich = useCallback(
|
|
35
|
+
async (contactId: string) => {
|
|
36
|
+
setIsEnriching(true)
|
|
37
|
+
setError(null)
|
|
38
|
+
try {
|
|
39
|
+
const enrichResult = await enrichFn(contactId)
|
|
40
|
+
setResult(enrichResult)
|
|
41
|
+
onSuccess?.(enrichResult)
|
|
42
|
+
} catch (err: unknown) {
|
|
43
|
+
setError(err instanceof Error ? err.message : 'Enrichment failed')
|
|
44
|
+
} finally {
|
|
45
|
+
setIsEnriching(false)
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
[enrichFn, onSuccess]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const reset = useCallback(() => {
|
|
52
|
+
setResult(null)
|
|
53
|
+
setError(null)
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
return { isEnriching, result, error, enrich, reset }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UseBatchEnrichmentOptions {
|
|
60
|
+
bulkEnrichFn: BulkEnrichFn
|
|
61
|
+
onComplete?: (results: EnrichmentResult[]) => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface UseBatchEnrichmentState {
|
|
65
|
+
isEnriching: boolean
|
|
66
|
+
progress: { completed: number; total: number }
|
|
67
|
+
results: EnrichmentResult[]
|
|
68
|
+
error: string | null
|
|
69
|
+
enrichBatch: (contactIds: string[]) => Promise<void>
|
|
70
|
+
cancel: () => void
|
|
71
|
+
reset: () => void
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function useBatchEnrichment({
|
|
75
|
+
bulkEnrichFn,
|
|
76
|
+
onComplete,
|
|
77
|
+
}: UseBatchEnrichmentOptions): UseBatchEnrichmentState {
|
|
78
|
+
const [isEnriching, setIsEnriching] = useState(false)
|
|
79
|
+
const [progress, setProgress] = useState({ completed: 0, total: 0 })
|
|
80
|
+
const [results, setResults] = useState<EnrichmentResult[]>([])
|
|
81
|
+
const [error, setError] = useState<string | null>(null)
|
|
82
|
+
const cancelledRef = useRef(false)
|
|
83
|
+
|
|
84
|
+
const enrichBatch = useCallback(
|
|
85
|
+
async (contactIds: string[]) => {
|
|
86
|
+
cancelledRef.current = false
|
|
87
|
+
setIsEnriching(true)
|
|
88
|
+
setError(null)
|
|
89
|
+
setProgress({ completed: 0, total: contactIds.length })
|
|
90
|
+
setResults([])
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const batchResults = await bulkEnrichFn(contactIds, (completed, total) => {
|
|
94
|
+
if (!cancelledRef.current) {
|
|
95
|
+
setProgress({ completed, total })
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
if (!cancelledRef.current) {
|
|
99
|
+
setResults(batchResults)
|
|
100
|
+
onComplete?.(batchResults)
|
|
101
|
+
}
|
|
102
|
+
} catch (err: unknown) {
|
|
103
|
+
if (!cancelledRef.current) {
|
|
104
|
+
setError(err instanceof Error ? err.message : 'Batch enrichment failed')
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
if (!cancelledRef.current) {
|
|
108
|
+
setIsEnriching(false)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[bulkEnrichFn, onComplete]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const cancel = useCallback(() => {
|
|
116
|
+
cancelledRef.current = true
|
|
117
|
+
setIsEnriching(false)
|
|
118
|
+
}, [])
|
|
119
|
+
|
|
120
|
+
const reset = useCallback(() => {
|
|
121
|
+
setResults([])
|
|
122
|
+
setError(null)
|
|
123
|
+
setProgress({ completed: 0, total: 0 })
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
126
|
+
return { isEnriching, progress, results, error, enrichBatch, cancel, reset }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface UseQueueStatusOptions {
|
|
130
|
+
fetchQueueStatus: QueueStatusFn
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface UseQueueStatusState {
|
|
134
|
+
queueStatus: QueueStatus | null
|
|
135
|
+
isLoading: boolean
|
|
136
|
+
refresh: () => Promise<void>
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useQueueStatus({ fetchQueueStatus }: UseQueueStatusOptions): UseQueueStatusState {
|
|
140
|
+
const [queueStatus, setQueueStatus] = useState<QueueStatus | null>(null)
|
|
141
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
142
|
+
|
|
143
|
+
const refresh = useCallback(async () => {
|
|
144
|
+
setIsLoading(true)
|
|
145
|
+
try {
|
|
146
|
+
const status = await fetchQueueStatus()
|
|
147
|
+
setQueueStatus(status)
|
|
148
|
+
} catch {
|
|
149
|
+
// Queue status may not be available
|
|
150
|
+
} finally {
|
|
151
|
+
setIsLoading(false)
|
|
152
|
+
}
|
|
153
|
+
}, [fetchQueueStatus])
|
|
154
|
+
|
|
155
|
+
return { queueStatus, isLoading, refresh }
|
|
156
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type { MessageTemplatesApi, MessageTemplateFilters, CreateMessageTemplateInput } from '@startsimpli/api'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List message templates with optional filters (paginated)
|
|
6
|
+
*/
|
|
7
|
+
export function useMessageTemplates(api: MessageTemplatesApi, filters?: MessageTemplateFilters) {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['messageTemplates', filters],
|
|
10
|
+
queryFn: () => api.list(filters),
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get a single message template by ID
|
|
16
|
+
*/
|
|
17
|
+
export function useMessageTemplate(api: MessageTemplatesApi, id: string) {
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: ['messageTemplates', id],
|
|
20
|
+
queryFn: () => api.get(id),
|
|
21
|
+
enabled: !!id,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a new message template
|
|
27
|
+
*/
|
|
28
|
+
export function useCreateMessageTemplate(api: MessageTemplatesApi) {
|
|
29
|
+
const queryClient = useQueryClient()
|
|
30
|
+
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: (data: CreateMessageTemplateInput) => api.create(data),
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: ['messageTemplates'] })
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Update an existing message template
|
|
41
|
+
*/
|
|
42
|
+
export function useUpdateMessageTemplate(api: MessageTemplatesApi) {
|
|
43
|
+
const queryClient = useQueryClient()
|
|
44
|
+
|
|
45
|
+
return useMutation({
|
|
46
|
+
mutationFn: ({ id, data }: { id: string; data: Partial<CreateMessageTemplateInput> }) =>
|
|
47
|
+
api.update(id, data),
|
|
48
|
+
onSuccess: (_, variables) => {
|
|
49
|
+
queryClient.invalidateQueries({ queryKey: ['messageTemplates'] })
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: ['messageTemplates', variables.id] })
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Delete a message template
|
|
57
|
+
*/
|
|
58
|
+
export function useDeleteMessageTemplate(api: MessageTemplatesApi) {
|
|
59
|
+
const queryClient = useQueryClient()
|
|
60
|
+
|
|
61
|
+
return useMutation({
|
|
62
|
+
mutationFn: (id: string) => api.delete(id),
|
|
63
|
+
onSuccess: () => {
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: ['messageTemplates'] })
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type { MessagesApi, MessageFilters, CreateMessageInput, ScheduleMessageInput, SendTestInput } from '@startsimpli/api'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List messages with optional filters (paginated)
|
|
6
|
+
*/
|
|
7
|
+
export function useMessages(api: MessagesApi, filters?: MessageFilters) {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['messages', filters],
|
|
10
|
+
queryFn: () => api.list(filters),
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get a single message by ID
|
|
16
|
+
*/
|
|
17
|
+
export function useMessage(api: MessagesApi, id: string) {
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: ['messages', id],
|
|
20
|
+
queryFn: () => api.get(id),
|
|
21
|
+
enabled: !!id,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a new message (draft)
|
|
27
|
+
*/
|
|
28
|
+
export function useCreateMessage(api: MessagesApi) {
|
|
29
|
+
const queryClient = useQueryClient()
|
|
30
|
+
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: (data: CreateMessageInput) => api.create(data),
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: ['messages'] })
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Send a message immediately
|
|
41
|
+
*/
|
|
42
|
+
export function useSendMessage(api: MessagesApi, id: string) {
|
|
43
|
+
const queryClient = useQueryClient()
|
|
44
|
+
|
|
45
|
+
return useMutation({
|
|
46
|
+
mutationFn: () => api.sendNow(id),
|
|
47
|
+
onSuccess: () => {
|
|
48
|
+
queryClient.invalidateQueries({ queryKey: ['messages'] })
|
|
49
|
+
queryClient.invalidateQueries({ queryKey: ['messages', id] })
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Schedule a message for future sending
|
|
56
|
+
*/
|
|
57
|
+
export function useScheduleMessage(api: MessagesApi, id: string) {
|
|
58
|
+
const queryClient = useQueryClient()
|
|
59
|
+
|
|
60
|
+
return useMutation({
|
|
61
|
+
mutationFn: (input: ScheduleMessageInput) => api.schedule(id, input),
|
|
62
|
+
onSuccess: () => {
|
|
63
|
+
queryClient.invalidateQueries({ queryKey: ['messages'] })
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: ['messages', id] })
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Send a test message
|
|
71
|
+
*/
|
|
72
|
+
export function useSendTestMessage(api: MessagesApi, id: string) {
|
|
73
|
+
return useMutation({
|
|
74
|
+
mutationFn: (input?: SendTestInput) => api.sendTest(id, input),
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* List available messaging channels
|
|
80
|
+
*/
|
|
81
|
+
export function useMessageChannels(api: MessagesApi) {
|
|
82
|
+
return useQuery({
|
|
83
|
+
queryKey: ['messages', 'channels'],
|
|
84
|
+
queryFn: () => api.getChannels(),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type {
|
|
3
|
+
TargetList,
|
|
4
|
+
TargetListMember,
|
|
5
|
+
CreateTargetListInput,
|
|
6
|
+
UpdateTargetListInput,
|
|
7
|
+
TargetListFilters,
|
|
8
|
+
TargetListMemberFilters,
|
|
9
|
+
AddMembersResult,
|
|
10
|
+
RemoveMembersResult,
|
|
11
|
+
RefreshResult,
|
|
12
|
+
PaginatedResponse,
|
|
13
|
+
} from '@startsimpli/api'
|
|
14
|
+
|
|
15
|
+
export const TARGET_LIST_KEYS = {
|
|
16
|
+
all: ['target-lists'] as const,
|
|
17
|
+
lists: (filters?: TargetListFilters) => [...TARGET_LIST_KEYS.all, filters] as const,
|
|
18
|
+
detail: (id: string) => ['target-list', id] as const,
|
|
19
|
+
members: (listId: string, filters?: TargetListMemberFilters) =>
|
|
20
|
+
['list-members', listId, filters] as const,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TargetListApiFns {
|
|
24
|
+
list: (filters?: TargetListFilters) => Promise<PaginatedResponse<TargetList>>
|
|
25
|
+
get: (id: string) => Promise<TargetList>
|
|
26
|
+
create: (data: CreateTargetListInput) => Promise<TargetList>
|
|
27
|
+
update: (id: string, data: UpdateTargetListInput) => Promise<TargetList>
|
|
28
|
+
delete: (id: string) => Promise<void>
|
|
29
|
+
getMembers: (listId: string, filters?: TargetListMemberFilters) => Promise<PaginatedResponse<TargetListMember>>
|
|
30
|
+
addMembers: (listId: string, contactIds: string[]) => Promise<AddMembersResult>
|
|
31
|
+
removeMembers: (listId: string, contactIds: string[]) => Promise<RemoveMembersResult>
|
|
32
|
+
refresh: (listId: string) => Promise<RefreshResult>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useTargetListDetail(
|
|
36
|
+
id: string,
|
|
37
|
+
apiFns: Pick<TargetListApiFns, 'get'>
|
|
38
|
+
) {
|
|
39
|
+
return useQuery({
|
|
40
|
+
queryKey: TARGET_LIST_KEYS.detail(id),
|
|
41
|
+
queryFn: () => apiFns.get(id),
|
|
42
|
+
enabled: !!id,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UseTargetListMutationsOptions {
|
|
47
|
+
apiFns: Pick<TargetListApiFns, 'create' | 'update' | 'delete' | 'addMembers' | 'removeMembers' | 'refresh'>
|
|
48
|
+
onSuccess?: (action: string) => void
|
|
49
|
+
onError?: (action: string) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useTargetListMutations({
|
|
53
|
+
apiFns,
|
|
54
|
+
onSuccess,
|
|
55
|
+
onError,
|
|
56
|
+
}: UseTargetListMutationsOptions) {
|
|
57
|
+
const queryClient = useQueryClient()
|
|
58
|
+
|
|
59
|
+
const invalidateList = (listId?: string) => {
|
|
60
|
+
queryClient.invalidateQueries({ queryKey: TARGET_LIST_KEYS.all })
|
|
61
|
+
if (listId) {
|
|
62
|
+
queryClient.invalidateQueries({ queryKey: TARGET_LIST_KEYS.detail(listId) })
|
|
63
|
+
queryClient.invalidateQueries({ queryKey: ['list-members', listId] })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const createList = useMutation({
|
|
68
|
+
mutationFn: (data: CreateTargetListInput) => apiFns.create(data),
|
|
69
|
+
onSuccess: () => {
|
|
70
|
+
invalidateList()
|
|
71
|
+
onSuccess?.('List created')
|
|
72
|
+
},
|
|
73
|
+
onError: () => onError?.('Failed to create list'),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const updateList = useMutation({
|
|
77
|
+
mutationFn: ({ id, data }: { id: string; data: UpdateTargetListInput }) =>
|
|
78
|
+
apiFns.update(id, data),
|
|
79
|
+
onSuccess: (_, variables) => {
|
|
80
|
+
invalidateList(variables.id)
|
|
81
|
+
onSuccess?.('List updated')
|
|
82
|
+
},
|
|
83
|
+
onError: () => onError?.('Failed to update list'),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const deleteList = useMutation({
|
|
87
|
+
mutationFn: (id: string) => apiFns.delete(id),
|
|
88
|
+
onSuccess: () => {
|
|
89
|
+
invalidateList()
|
|
90
|
+
onSuccess?.('List deleted')
|
|
91
|
+
},
|
|
92
|
+
onError: () => onError?.('Failed to delete list'),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const addMembersMutation = useMutation({
|
|
96
|
+
mutationFn: ({ listId, contactIds }: { listId: string; contactIds: string[] }) =>
|
|
97
|
+
apiFns.addMembers(listId, contactIds),
|
|
98
|
+
onSuccess: (_, variables) => {
|
|
99
|
+
invalidateList(variables.listId)
|
|
100
|
+
onSuccess?.('Members added')
|
|
101
|
+
},
|
|
102
|
+
onError: () => onError?.('Failed to add members'),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const removeMembersMutation = useMutation({
|
|
106
|
+
mutationFn: ({ listId, contactIds }: { listId: string; contactIds: string[] }) =>
|
|
107
|
+
apiFns.removeMembers(listId, contactIds),
|
|
108
|
+
onSuccess: (_, variables) => {
|
|
109
|
+
invalidateList(variables.listId)
|
|
110
|
+
onSuccess?.('Members removed')
|
|
111
|
+
},
|
|
112
|
+
onError: () => onError?.('Failed to remove members'),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const refreshList = useMutation({
|
|
116
|
+
mutationFn: (listId: string) => apiFns.refresh(listId),
|
|
117
|
+
onSuccess: (_, listId) => {
|
|
118
|
+
invalidateList(listId)
|
|
119
|
+
onSuccess?.('List refreshed')
|
|
120
|
+
},
|
|
121
|
+
onError: () => onError?.('Failed to refresh list'),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
createList,
|
|
126
|
+
updateList,
|
|
127
|
+
deleteList,
|
|
128
|
+
addMembers: addMembersMutation,
|
|
129
|
+
removeMembers: removeMembersMutation,
|
|
130
|
+
refreshList,
|
|
131
|
+
}
|
|
132
|
+
}
|