feat: email infrastructure frontend — settings, templates, and log tabs
Adds three new tabs to the organisation settings page: - E-mail opmaak: replaces old EmailBrandingTab to use the new organisation_email_settings API (logo, colors, footer, reply-to) - E-mail templates: list/edit/preview/test/reset all 6 template types with variable hints, defaults comparison, and iframe preview - E-mail log: server-side paginated table with filters (search, status, type, date range), status chips, and expandable row details Supporting files: - types/email.ts: TypeScript interfaces for settings, templates, logs - composables/api/useEmail.ts: TanStack Query hooks for all email endpoints Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
164
apps/app/src/composables/api/useEmail.ts
Normal file
164
apps/app/src/composables/api/useEmail.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import type { Ref } from 'vue'
|
||||
import { apiClient } from '@/lib/axios'
|
||||
import type {
|
||||
EmailLog,
|
||||
EmailLogFilters,
|
||||
EmailSettings,
|
||||
EmailSettingsDefaults,
|
||||
EmailTemplate,
|
||||
UpdateEmailSettingsPayload,
|
||||
UpdateEmailTemplatePayload,
|
||||
} from '@/types/email'
|
||||
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data: T
|
||||
message?: string
|
||||
}
|
||||
|
||||
interface PaginatedResponse<T> {
|
||||
data: T[]
|
||||
links: Record<string, string | null>
|
||||
meta: {
|
||||
current_page: number
|
||||
per_page: number
|
||||
total: number
|
||||
last_page: number
|
||||
}
|
||||
}
|
||||
|
||||
// ── Email Settings ──
|
||||
|
||||
export function useEmailSettings(orgId: Ref<string>) {
|
||||
return useQuery({
|
||||
queryKey: ['email-settings', orgId],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get<ApiResponse<EmailSettings | EmailSettingsDefaults>>(
|
||||
`/organisations/${orgId.value}/email-settings`,
|
||||
)
|
||||
|
||||
return data.data
|
||||
},
|
||||
enabled: () => !!orgId.value,
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdateEmailSettings(orgId: Ref<string>) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (payload: UpdateEmailSettingsPayload) => {
|
||||
const { data } = await apiClient.put<ApiResponse<EmailSettings>>(
|
||||
`/organisations/${orgId.value}/email-settings`,
|
||||
payload,
|
||||
)
|
||||
|
||||
return data.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['email-settings', orgId.value] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ── Email Templates ──
|
||||
|
||||
export function useEmailTemplates(orgId: Ref<string>) {
|
||||
return useQuery({
|
||||
queryKey: ['email-templates', orgId],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get<ApiResponse<EmailTemplate[]>>(
|
||||
`/organisations/${orgId.value}/email-templates`,
|
||||
)
|
||||
|
||||
return data.data
|
||||
},
|
||||
enabled: () => !!orgId.value,
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdateEmailTemplate(orgId: Ref<string>) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ type, ...payload }: UpdateEmailTemplatePayload & { type: string }) => {
|
||||
const { data } = await apiClient.put<ApiResponse<EmailTemplate>>(
|
||||
`/organisations/${orgId.value}/email-templates/${type}`,
|
||||
payload,
|
||||
)
|
||||
|
||||
return data.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['email-templates', orgId.value] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useResetEmailTemplate(orgId: Ref<string>) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (type: string) => {
|
||||
await apiClient.delete(`/organisations/${orgId.value}/email-templates/${type}`)
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['email-templates', orgId.value] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function usePreviewEmailTemplate(orgId: Ref<string>) {
|
||||
return useMutation({
|
||||
mutationFn: async (type: string) => {
|
||||
const { data } = await apiClient.post<ApiResponse<{ html: string }>>(
|
||||
`/organisations/${orgId.value}/email-templates/${type}/preview`,
|
||||
)
|
||||
|
||||
return data.data.html
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useSendTestEmail(orgId: Ref<string>) {
|
||||
return useMutation({
|
||||
mutationFn: async ({ type, email }: { type: string; email: string }) => {
|
||||
const { data } = await apiClient.post<ApiResponse<null>>(
|
||||
`/organisations/${orgId.value}/email-templates/${type}/send-test`,
|
||||
{ email },
|
||||
)
|
||||
|
||||
return data
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ── Email Logs ──
|
||||
|
||||
export function useEmailLogs(orgId: Ref<string>, filters: Ref<EmailLogFilters>) {
|
||||
return useQuery({
|
||||
queryKey: ['email-logs', orgId, filters],
|
||||
queryFn: async () => {
|
||||
const params: Record<string, string | number> = {
|
||||
page: filters.value.page,
|
||||
per_page: filters.value.perPage,
|
||||
}
|
||||
|
||||
if (filters.value.search) params.search = filters.value.search
|
||||
if (filters.value.status) params.status = filters.value.status
|
||||
if (filters.value.templateType) params.template_type = filters.value.templateType
|
||||
if (filters.value.eventId) params.event_id = filters.value.eventId
|
||||
if (filters.value.from) params.from = filters.value.from
|
||||
if (filters.value.to) params.to = filters.value.to
|
||||
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResponse<EmailLog>>>(
|
||||
`/organisations/${orgId.value}/email-logs`,
|
||||
{ params },
|
||||
)
|
||||
|
||||
return data.data
|
||||
},
|
||||
enabled: () => !!orgId.value,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user