mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Merge pull request #12 from ArnasDon/fix/send-route-and-handler
fix: outbound messages silently failing (Meta args + schema + silent errors)
This commit is contained in:
commit
0a5d274e02
@ -156,6 +156,15 @@ export default function InboxPage() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleUpdateMessage = useCallback(
|
||||
(id: string, updates: Partial<Message>) => {
|
||||
setMessages((prev) =>
|
||||
prev.map((m) => (m.id === id ? { ...m, ...updates } : m))
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleStatusChange = useCallback(
|
||||
(conversationId: string, status: ConversationStatus) => {
|
||||
setConversations((prev) =>
|
||||
@ -197,6 +206,7 @@ export default function InboxPage() {
|
||||
messages={messages}
|
||||
onMessagesLoaded={handleMessagesLoaded}
|
||||
onNewMessage={handleNewMessage}
|
||||
onUpdateMessage={handleUpdateMessage}
|
||||
onStatusChange={handleStatusChange}
|
||||
/>
|
||||
|
||||
|
||||
@ -100,40 +100,57 @@ export async function POST(request: Request) {
|
||||
const accessToken = decrypt(config.access_token)
|
||||
|
||||
// Send via Meta API
|
||||
// Function signatures are:
|
||||
// sendTextMessage(phoneNumberId, accessToken, to, text)
|
||||
// sendTemplateMessage(phoneNumberId, accessToken, to, templateName, language?, params?)
|
||||
// The args were previously swapped (accessToken before phoneNumberId),
|
||||
// causing Meta to reject every send.
|
||||
let waMessageId: string
|
||||
|
||||
if (message_type === 'template') {
|
||||
const result = await sendTemplateMessage(
|
||||
accessToken,
|
||||
config.phone_number_id,
|
||||
sanitizedPhone,
|
||||
template_name,
|
||||
template_params || []
|
||||
try {
|
||||
if (message_type === 'template') {
|
||||
const result = await sendTemplateMessage(
|
||||
config.phone_number_id,
|
||||
accessToken,
|
||||
sanitizedPhone,
|
||||
template_name,
|
||||
undefined, // use default language (en_US)
|
||||
template_params || []
|
||||
)
|
||||
waMessageId = result.messageId
|
||||
} else {
|
||||
const result = await sendTextMessage(
|
||||
config.phone_number_id,
|
||||
accessToken,
|
||||
sanitizedPhone,
|
||||
content_text
|
||||
)
|
||||
waMessageId = result.messageId
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown Meta API error'
|
||||
console.error('Meta API send failed:', message)
|
||||
return NextResponse.json(
|
||||
{ error: `Meta API error: ${message}` },
|
||||
{ status: 502 }
|
||||
)
|
||||
waMessageId = result.messageId
|
||||
} else {
|
||||
const result = await sendTextMessage(
|
||||
accessToken,
|
||||
config.phone_number_id,
|
||||
sanitizedPhone,
|
||||
content_text
|
||||
)
|
||||
waMessageId = result.messageId
|
||||
}
|
||||
|
||||
// Insert message into DB
|
||||
// Insert message into DB — field names MUST match the messages schema
|
||||
// (see supabase/migrations/001_initial_schema.sql):
|
||||
// conversation_id, sender_type, content_type, content_text,
|
||||
// media_url, template_name, message_id, status, created_at
|
||||
const { data: messageRecord, error: msgError } = await supabase
|
||||
.from('messages')
|
||||
.insert({
|
||||
conversation_id,
|
||||
user_id: user.id,
|
||||
whatsapp_message_id: waMessageId,
|
||||
direction: 'outbound',
|
||||
message_type,
|
||||
sender_type: 'agent',
|
||||
content_type: message_type,
|
||||
content_text: content_text || null,
|
||||
media_url: media_url || null,
|
||||
template_name: template_name || null,
|
||||
message_id: waMessageId,
|
||||
status: 'sent',
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
@ -141,7 +158,7 @@ export async function POST(request: Request) {
|
||||
if (msgError) {
|
||||
console.error('Error inserting sent message:', msgError)
|
||||
return NextResponse.json(
|
||||
{ error: 'Message sent but failed to save to database' },
|
||||
{ error: `Message sent to Meta but failed to save to DB: ${msgError.message}` },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { MessageBubble } from "./message-bubble";
|
||||
import { MessageComposer } from "./message-composer";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface MessageThreadProps {
|
||||
conversation: Conversation | null;
|
||||
@ -29,6 +30,7 @@ interface MessageThreadProps {
|
||||
messages: Message[];
|
||||
onMessagesLoaded: (messages: Message[]) => void;
|
||||
onNewMessage: (message: Message) => void;
|
||||
onUpdateMessage: (id: string, updates: Partial<Message>) => void;
|
||||
onStatusChange: (conversationId: string, status: ConversationStatus) => void;
|
||||
}
|
||||
|
||||
@ -68,6 +70,7 @@ export function MessageThread({
|
||||
messages,
|
||||
onMessagesLoaded,
|
||||
onNewMessage,
|
||||
onUpdateMessage,
|
||||
onStatusChange,
|
||||
}: MessageThreadProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -169,9 +172,11 @@ export function MessageThread({
|
||||
async (text: string) => {
|
||||
if (!conversation) return;
|
||||
|
||||
// Optimistic update
|
||||
const tempId = `temp-${Date.now()}`;
|
||||
|
||||
// Optimistic update — shows the message immediately with "sending" status
|
||||
const optimisticMsg: Message = {
|
||||
id: `temp-${Date.now()}`,
|
||||
id: tempId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: "agent",
|
||||
content_type: "text",
|
||||
@ -192,14 +197,29 @@ export function MessageThread({
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("Failed to send message");
|
||||
const reason = payload?.error || `HTTP ${res.status}`;
|
||||
console.error("Failed to send message:", reason);
|
||||
toast.error(`Failed to send: ${reason}`);
|
||||
// Mark the optimistic bubble as failed so the user sees what happened
|
||||
onUpdateMessage(tempId, { status: "failed" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Success — the realtime INSERT event will replace the temp bubble
|
||||
// with the real DB row. If realtime hasn't arrived yet, at least
|
||||
// flip status to 'sent' so the UI stops showing "sending".
|
||||
onUpdateMessage(tempId, { status: "sent" });
|
||||
} catch (err) {
|
||||
console.error("Failed to send message:", err);
|
||||
const reason = err instanceof Error ? err.message : "network error";
|
||||
toast.error(`Failed to send: ${reason}`);
|
||||
onUpdateMessage(tempId, { status: "failed" });
|
||||
}
|
||||
},
|
||||
[conversation, onNewMessage]
|
||||
[conversation, onNewMessage, onUpdateMessage]
|
||||
);
|
||||
|
||||
const handleStatusChange = useCallback(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user