| 1 |
import { apiClient } from "../lib/api-client"; |
| 2 |
|
| 3 |
/** WhatsApp module meta — drives gate states on the settings page. */ |
| 4 |
export interface WhatsappMeta { |
| 5 |
is_eligible: boolean; |
| 6 |
is_module_enabled: boolean; |
| 7 |
is_configured: boolean; |
| 8 |
providers: Array<{ id: string; label: string; disabled?: boolean }>; |
| 9 |
/** Meta webhook setup info — operator pastes these into WhatsApp |
| 10 |
* Business Manager → Webhooks subscription. `app_secret_configured` |
| 11 |
* is a visibility flag only; the secret itself is encrypted at rest |
| 12 |
* and never sent to the client. */ |
| 13 |
webhook: { |
| 14 |
url: string; |
| 15 |
verify_token: string; |
| 16 |
app_secret_configured: boolean; |
| 17 |
}; |
| 18 |
upgrade_url: string; |
| 19 |
docs_url: string; |
| 20 |
} |
| 21 |
|
| 22 |
/** Public-facing click-to-WhatsApp widget configuration. */ |
| 23 |
export interface WhatsappWidgetSettings { |
| 24 |
enabled: boolean; |
| 25 |
/** E.164 phone the widget opens against. */ |
| 26 |
contact_phone: string; |
| 27 |
/** Pre-filled message body. Supports `{{trip_name}}` on trip pages. */ |
| 28 |
prefilled_message: string; |
| 29 |
/** 'all' = every frontend page, 'trips_only' = single-trip pages only. */ |
| 30 |
display_on: "all" | "trips_only"; |
| 31 |
position: "bottom-right" | "bottom-left"; |
| 32 |
/** Pill label next to the WhatsApp icon. Empty = icon only. */ |
| 33 |
label: string; |
| 34 |
/** Render an in-page CTA inside the single-trip sidebar. */ |
| 35 |
show_on_trip_sidebar: boolean; |
| 36 |
} |
| 37 |
|
| 38 |
/** Non-template settings (provider config + opt-in + admin phone). */ |
| 39 |
export interface WhatsappSettings { |
| 40 |
active_provider: string; |
| 41 |
phone_number_id: string; |
| 42 |
business_account_id: string; |
| 43 |
sender_display_name: string; |
| 44 |
default_country_code: string; |
| 45 |
/** Destination phone for templates with recipient_type=admin. E.164. */ |
| 46 |
admin_phone: string; |
| 47 |
opt_in_required: boolean; |
| 48 |
opt_in_copy: string; |
| 49 |
reminder_hours_before: number; |
| 50 |
review_hours_after: number; |
| 51 |
widget: WhatsappWidgetSettings; |
| 52 |
} |
| 53 |
|
| 54 |
export interface WhatsappCredentialStatus { |
| 55 |
configured: boolean; |
| 56 |
hint: string; |
| 57 |
} |
| 58 |
|
| 59 |
export interface WhatsappSettingsResponse { |
| 60 |
settings: WhatsappSettings; |
| 61 |
credentials: Record<string, Record<string, WhatsappCredentialStatus>>; |
| 62 |
} |
| 63 |
|
| 64 |
/** One row from /whatsapp/events — drives the Trigger Event dropdown. */ |
| 65 |
export interface WhatsappEvent { |
| 66 |
key: string; |
| 67 |
name: string; |
| 68 |
description: string; |
| 69 |
kind: "action" | "time"; |
| 70 |
wp_hook: string | null; |
| 71 |
/** Per-event variables. Each `{ key, description }` becomes a sidebar button. */ |
| 72 |
variables: Array<{ key: string; description: string }>; |
| 73 |
} |
| 74 |
|
| 75 |
/** A DB row from `yatra_whatsapp_templates`. */ |
| 76 |
export interface WhatsappTemplate { |
| 77 |
id: number; |
| 78 |
template_key: string; |
| 79 |
event_key: string; |
| 80 |
is_system: boolean; |
| 81 |
is_active: boolean; |
| 82 |
name: string; |
| 83 |
description: string | null; |
| 84 |
body: string | null; |
| 85 |
recipient_type: "customer" | "admin"; |
| 86 |
/** Flattened Cloud-API specifics for convenience (also in settings JSON). */ |
| 87 |
meta_template_name: string; |
| 88 |
language_code: string; |
| 89 |
settings: Record<string, unknown>; |
| 90 |
created_at: string; |
| 91 |
updated_at: string; |
| 92 |
} |
| 93 |
|
| 94 |
export interface WhatsappMessageRow { |
| 95 |
id: number; |
| 96 |
direction: "outbound" | "inbound"; |
| 97 |
phone: string; |
| 98 |
customer_id: number | null; |
| 99 |
booking_id: number | null; |
| 100 |
template_key: string | null; |
| 101 |
body: string | null; |
| 102 |
status: string; |
| 103 |
provider_message_id: string | null; |
| 104 |
error_message: string | null; |
| 105 |
created_at: string; |
| 106 |
updated_at: string; |
| 107 |
} |
| 108 |
|
| 109 |
export interface WhatsappOptInRow { |
| 110 |
phone: string; |
| 111 |
opted_in: boolean; |
| 112 |
source: string; |
| 113 |
updated_at: string; |
| 114 |
} |
| 115 |
|
| 116 |
export interface WhatsappTestSendResult { |
| 117 |
ok: boolean; |
| 118 |
provider_message_id?: string; |
| 119 |
error?: string; |
| 120 |
} |
| 121 |
|
| 122 |
export const whatsappApi = { |
| 123 |
getMeta: () => apiClient.get("/whatsapp/meta") as Promise<WhatsappMeta>, |
| 124 |
|
| 125 |
getSettings: () => |
| 126 |
apiClient.get("/whatsapp/settings") as Promise<WhatsappSettingsResponse>, |
| 127 |
|
| 128 |
updateSettings: (patch: Partial<WhatsappSettings>) => |
| 129 |
apiClient.put("/whatsapp/settings", { settings: patch }) as Promise<{ |
| 130 |
settings: WhatsappSettings; |
| 131 |
message: string; |
| 132 |
}>, |
| 133 |
|
| 134 |
updateCredential: (provider: string, field: string, value: string) => |
| 135 |
apiClient.put("/whatsapp/credentials", { |
| 136 |
provider, |
| 137 |
field, |
| 138 |
value, |
| 139 |
}) as Promise<{ |
| 140 |
credentials: Record<string, Record<string, WhatsappCredentialStatus>>; |
| 141 |
message: string; |
| 142 |
}>, |
| 143 |
|
| 144 |
listEvents: () => |
| 145 |
apiClient.get("/whatsapp/events") as Promise<{ data: WhatsappEvent[] }>, |
| 146 |
|
| 147 |
listTemplates: () => |
| 148 |
apiClient.get("/whatsapp/templates") as Promise<{ |
| 149 |
data: WhatsappTemplate[]; |
| 150 |
}>, |
| 151 |
|
| 152 |
getTemplate: (id: number) => |
| 153 |
apiClient.get(`/whatsapp/templates/${id}`) as Promise<{ |
| 154 |
data: WhatsappTemplate; |
| 155 |
}>, |
| 156 |
|
| 157 |
createTemplate: (payload: Partial<WhatsappTemplate>) => |
| 158 |
apiClient.post("/whatsapp/templates", payload) as Promise<{ |
| 159 |
data: WhatsappTemplate; |
| 160 |
message: string; |
| 161 |
}>, |
| 162 |
|
| 163 |
updateTemplate: (id: number, payload: Partial<WhatsappTemplate>) => |
| 164 |
apiClient.put(`/whatsapp/templates/${id}`, payload) as Promise<{ |
| 165 |
data: WhatsappTemplate; |
| 166 |
message: string; |
| 167 |
}>, |
| 168 |
|
| 169 |
deleteTemplate: (id: number) => |
| 170 |
apiClient.delete(`/whatsapp/templates/${id}`) as Promise<{ |
| 171 |
message: string; |
| 172 |
}>, |
| 173 |
|
| 174 |
/** |
| 175 |
* Re-seed the bundled defaults. Useful when the operator has missed |
| 176 |
* a system template (deleted accidentally pre-lockdown, or a |
| 177 |
* first-install race left them with none). |
| 178 |
*/ |
| 179 |
restoreDefaults: () => |
| 180 |
apiClient.post("/whatsapp/templates/restore-defaults", {}) as Promise<{ |
| 181 |
data: WhatsappTemplate[]; |
| 182 |
message: string; |
| 183 |
}>, |
| 184 |
|
| 185 |
testSendTemplate: ( |
| 186 |
id: number, |
| 187 |
params: { phone?: string; booking_id?: number }, |
| 188 |
) => |
| 189 |
apiClient.post( |
| 190 |
`/whatsapp/templates/${id}/test-send`, |
| 191 |
params, |
| 192 |
) as Promise<WhatsappTestSendResult>, |
| 193 |
|
| 194 |
/** |
| 195 |
* Paginated message log. Returns `total` so the UI can render correct |
| 196 |
* page counts without local slicing. `page` is 1-indexed. |
| 197 |
*/ |
| 198 |
listMessages: ( |
| 199 |
params: { |
| 200 |
page?: number; |
| 201 |
per_page?: number; |
| 202 |
status?: string; |
| 203 |
phone?: string; |
| 204 |
} = {}, |
| 205 |
) => |
| 206 |
apiClient.get("/whatsapp/messages", { |
| 207 |
params: { |
| 208 |
page: params.page ?? 1, |
| 209 |
per_page: params.per_page ?? 20, |
| 210 |
...(params.status ? { status: params.status } : {}), |
| 211 |
...(params.phone ? { phone: params.phone } : {}), |
| 212 |
}, |
| 213 |
}) as Promise<{ |
| 214 |
data: WhatsappMessageRow[]; |
| 215 |
total: number; |
| 216 |
page: number; |
| 217 |
per_page: number; |
| 218 |
}>, |
| 219 |
|
| 220 |
listOptIns: () => |
| 221 |
apiClient.get("/whatsapp/opt-ins") as Promise<{ data: WhatsappOptInRow[] }>, |
| 222 |
|
| 223 |
/* ----------------------- template versions ----------------------- */ |
| 224 |
/** |
| 225 |
* Append-only edit history for a template. Returned newest-first. |
| 226 |
* Operators restore from this list when a recent edit needs rollback. |
| 227 |
*/ |
| 228 |
listTemplateVersions: (id: number) => |
| 229 |
apiClient.get(`/whatsapp/templates/${id}/versions`) as Promise<{ |
| 230 |
data: WhatsappTemplateVersion[]; |
| 231 |
}>, |
| 232 |
|
| 233 |
/** |
| 234 |
* Restore the template to a prior snapshot. Server snapshots the |
| 235 |
* CURRENT state first so the chain stays linear ("undo this restore" |
| 236 |
* is just another restore from the most-recent row). |
| 237 |
*/ |
| 238 |
restoreTemplateVersion: (id: number, versionId: number) => |
| 239 |
apiClient.post( |
| 240 |
`/whatsapp/templates/${id}/versions/${versionId}/restore`, |
| 241 |
{}, |
| 242 |
) as Promise<{ |
| 243 |
data: WhatsappTemplate; |
| 244 |
message: string; |
| 245 |
}>, |
| 246 |
}; |
| 247 |
|
| 248 |
/* -------------------------------------------------------------------------- */ |
| 249 |
/* Template version-history shape */ |
| 250 |
/* -------------------------------------------------------------------------- */ |
| 251 |
|
| 252 |
/** One snapshot of a template at a moment in time. */ |
| 253 |
export interface WhatsappTemplateVersion { |
| 254 |
id: number; |
| 255 |
template_id: number; |
| 256 |
/** Monotonic per template — UI displays as "v3". */ |
| 257 |
version_number: number; |
| 258 |
/** Frozen copy of the row state at the time of the change. Subset |
| 259 |
* of the fields on WhatsappTemplate plus settings as plain object. */ |
| 260 |
snapshot: { |
| 261 |
name?: string; |
| 262 |
description?: string; |
| 263 |
body?: string; |
| 264 |
event_key?: string; |
| 265 |
recipient_type?: "customer" | "admin"; |
| 266 |
is_active?: boolean; |
| 267 |
settings?: Record<string, unknown>; |
| 268 |
is_system?: boolean; |
| 269 |
}; |
| 270 |
/** Short human hint of what changed in THIS edit (e.g. "edited: |
| 271 |
* body, is_active") or "Restored from vN" for restore rows. */ |
| 272 |
change_summary: string | null; |
| 273 |
created_by: number | null; |
| 274 |
created_at: string; |
| 275 |
} |
| 276 |
|