Add SMS templates UI, prepaid balance settings, and list filter layout.

Business settings manage templates and balance; Super Admin verifies templates and tops up via SMS settings; filters use a separate panel with even field gaps.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-08 16:24:26 +03:30
co-authored by Cursor
parent 8f0b4cef85
commit 0eb19bbebd
27 changed files with 2324 additions and 527 deletions
+15 -3
View File
@@ -193,22 +193,34 @@ export interface SendCustomersBulkSmsResult {
source?: string
}
export async function sendCustomerSms(userId: string, message: string) {
export async function sendCustomerSms(
userId: string,
message: string,
templateId?: string | null,
) {
return apiRequest<SendCustomerSmsResult>(businessPath(`/${userId}/sms`), {
method: 'POST',
auth: true,
body: { message },
body: {
message,
...(templateId ? { templateId } : {}),
},
})
}
/** Bulk SMS via Gama SendBulk. Omit userIds (or pass []) to send to all enabled customers. */
export async function sendCustomersBulkSms(message: string, userIds?: string[]) {
export async function sendCustomersBulkSms(
message: string,
userIds?: string[],
templateId?: string | null,
) {
return apiRequest<SendCustomersBulkSmsResult>(businessPath('/sms'), {
method: 'POST',
auth: true,
body: {
message,
...(userIds && userIds.length > 0 ? { userIds } : {}),
...(templateId ? { templateId } : {}),
},
})
}
@@ -107,12 +107,17 @@ export interface WebsiteSettings {
sitemapConfig?: WebsiteSitemapConfig | null
}
export interface SmsSettings {
senderNumber: string | null
}
export interface BusinessSettings {
branding: BrandingSettings
dashboard: DashboardSettings
store: StoreSettings
modules?: ModulesSettings
website?: WebsiteSettings
sms?: SmsSettings
}
export interface SettingsResponse {
@@ -0,0 +1,75 @@
import { apiRequest } from '../lib/api'
import { getActiveBusinessId } from '../lib/businessContext'
export type SmsTemplateVerificationStatus = 'free' | 'pending' | 'verified'
export interface SmsTemplate {
id: string
businessId: string
name: string
body: string
verificationStatus: SmsTemplateVerificationStatus
isVerified: boolean
verifiedAt: string | null
verifiedBy: string | null
createdAt: string
updatedAt: string
}
function smsPath(suffix = '') {
const businessId = getActiveBusinessId()
if (!businessId) {
throw new Error('No active business selected. Please sign in again.')
}
return `/businesses/${businessId}/sms${suffix}`
}
export async function listSmsTemplates(signal?: AbortSignal) {
return apiRequest<{ items: SmsTemplate[]; variables: string[] }>(
smsPath('/templates'),
{ auth: true, signal },
)
}
export async function createSmsTemplate(payload: { name: string; body: string }) {
return apiRequest<{ template: SmsTemplate }>(smsPath('/templates'), {
method: 'POST',
auth: true,
body: payload,
})
}
export async function updateSmsTemplate(
templateId: string,
payload: { name?: string; body?: string },
) {
return apiRequest<{ template: SmsTemplate }>(smsPath(`/templates/${templateId}`), {
method: 'PATCH',
auth: true,
body: payload,
})
}
export async function requestSmsTemplateVerification(templateId: string) {
return apiRequest<{ template: SmsTemplate }>(
smsPath(`/templates/${templateId}/request-verification`),
{ method: 'POST', auth: true },
)
}
export async function deleteSmsTemplate(templateId: string) {
return apiRequest<{ success: boolean }>(smsPath(`/templates/${templateId}`), {
method: 'DELETE',
auth: true,
})
}
export async function getSmsCredit(signal?: AbortSignal) {
return apiRequest<{
balance: number
/** @deprecated use balance */
credit: number | null
currency: string | null
message: string | null
}>(smsPath('/credit'), { auth: true, signal })
}