Add customer SMS/Excel tools, selection bar, and sticky import toasts.

Bulk and single SMS from Users, row selection with overflow summary, persistent large import toasts, and business SMS sender in Super Admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-08 14:12:57 +03:30
co-authored by Cursor
parent 2a32c46b2e
commit 4c3bf8324e
30 changed files with 1597 additions and 149 deletions
+82 -1
View File
@@ -1,4 +1,4 @@
import { apiRequest } from '../lib/api'
import { apiRequest, apiRequestBlob, apiRequestForm } from '../lib/api'
import { getActiveBusinessId } from '../lib/businessContext'
export interface BusinessCustomer {
@@ -131,3 +131,84 @@ export async function searchCustomers(
{ auth: true, signal },
)
}
export async function downloadCustomersExcel() {
const { blob } = await apiRequestBlob(businessPath('/export'), { auth: true })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = customersExcelFilename()
document.body.appendChild(link)
link.click()
link.remove()
URL.revokeObjectURL(url)
}
function customersExcelFilename(now = new Date()) {
const parts = new Intl.DateTimeFormat('en-GB', {
timeZone: 'Asia/Tehran',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
}).formatToParts(now)
const pick = (type: Intl.DateTimeFormatPartTypes) =>
parts.find((part) => part.type === type)?.value ?? ''
return `customers - ${pick('year')}-${pick('month')}-${pick('day')} - ${pick('hour')}-${pick('minute')}.xlsx`
}
export interface CustomersExcelImportResult {
message: string
total: number
created: number
linked: number
skipped: number
skipSamples: string[]
}
export async function importCustomersExcel(file: File) {
const formData = new FormData()
formData.append('file', file)
return apiRequestForm<CustomersExcelImportResult>(businessPath('/import'), formData, {
auth: true,
})
}
export interface SendCustomerSmsResult {
enabled: boolean
message: string
serverId?: string
source?: string
}
export interface SendCustomersBulkSmsResult {
enabled: boolean
message: string
recipientCount: number
serverIds?: string[]
source?: string
}
export async function sendCustomerSms(userId: string, message: string) {
return apiRequest<SendCustomerSmsResult>(businessPath(`/${userId}/sms`), {
method: 'POST',
auth: true,
body: { message },
})
}
/** Bulk SMS via Gama SendBulk. Omit userIds (or pass []) to send to all enabled customers. */
export async function sendCustomersBulkSms(message: string, userIds?: string[]) {
return apiRequest<SendCustomersBulkSmsResult>(businessPath('/sms'), {
method: 'POST',
auth: true,
body: {
message,
...(userIds && userIds.length > 0 ? { userIds } : {}),
},
})
}