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(
|
const handleStatusChange = useCallback(
|
||||||
(conversationId: string, status: ConversationStatus) => {
|
(conversationId: string, status: ConversationStatus) => {
|
||||||
setConversations((prev) =>
|
setConversations((prev) =>
|
||||||
@ -197,6 +206,7 @@ export default function InboxPage() {
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
onMessagesLoaded={handleMessagesLoaded}
|
onMessagesLoaded={handleMessagesLoaded}
|
||||||
onNewMessage={handleNewMessage}
|
onNewMessage={handleNewMessage}
|
||||||
|
onUpdateMessage={handleUpdateMessage}
|
||||||
onStatusChange={handleStatusChange}
|
onStatusChange={handleStatusChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -100,40 +100,57 @@ export async function POST(request: Request) {
|
|||||||
const accessToken = decrypt(config.access_token)
|
const accessToken = decrypt(config.access_token)
|
||||||
|
|
||||||
// Send via Meta API
|
// 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
|
let waMessageId: string
|
||||||
|
|
||||||
|
try {
|
||||||
if (message_type === 'template') {
|
if (message_type === 'template') {
|
||||||
const result = await sendTemplateMessage(
|
const result = await sendTemplateMessage(
|
||||||
accessToken,
|
|
||||||
config.phone_number_id,
|
config.phone_number_id,
|
||||||
|
accessToken,
|
||||||
sanitizedPhone,
|
sanitizedPhone,
|
||||||
template_name,
|
template_name,
|
||||||
|
undefined, // use default language (en_US)
|
||||||
template_params || []
|
template_params || []
|
||||||
)
|
)
|
||||||
waMessageId = result.messageId
|
waMessageId = result.messageId
|
||||||
} else {
|
} else {
|
||||||
const result = await sendTextMessage(
|
const result = await sendTextMessage(
|
||||||
accessToken,
|
|
||||||
config.phone_number_id,
|
config.phone_number_id,
|
||||||
|
accessToken,
|
||||||
sanitizedPhone,
|
sanitizedPhone,
|
||||||
content_text
|
content_text
|
||||||
)
|
)
|
||||||
waMessageId = result.messageId
|
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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
const { data: messageRecord, error: msgError } = await supabase
|
||||||
.from('messages')
|
.from('messages')
|
||||||
.insert({
|
.insert({
|
||||||
conversation_id,
|
conversation_id,
|
||||||
user_id: user.id,
|
sender_type: 'agent',
|
||||||
whatsapp_message_id: waMessageId,
|
content_type: message_type,
|
||||||
direction: 'outbound',
|
|
||||||
message_type,
|
|
||||||
content_text: content_text || null,
|
content_text: content_text || null,
|
||||||
media_url: media_url || null,
|
media_url: media_url || null,
|
||||||
|
template_name: template_name || null,
|
||||||
|
message_id: waMessageId,
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
.single()
|
.single()
|
||||||
@ -141,7 +158,7 @@ export async function POST(request: Request) {
|
|||||||
if (msgError) {
|
if (msgError) {
|
||||||
console.error('Error inserting sent message:', msgError)
|
console.error('Error inserting sent message:', msgError)
|
||||||
return NextResponse.json(
|
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 }
|
{ status: 500 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { MessageBubble } from "./message-bubble";
|
import { MessageBubble } from "./message-bubble";
|
||||||
import { MessageComposer } from "./message-composer";
|
import { MessageComposer } from "./message-composer";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface MessageThreadProps {
|
interface MessageThreadProps {
|
||||||
conversation: Conversation | null;
|
conversation: Conversation | null;
|
||||||
@ -29,6 +30,7 @@ interface MessageThreadProps {
|
|||||||
messages: Message[];
|
messages: Message[];
|
||||||
onMessagesLoaded: (messages: Message[]) => void;
|
onMessagesLoaded: (messages: Message[]) => void;
|
||||||
onNewMessage: (message: Message) => void;
|
onNewMessage: (message: Message) => void;
|
||||||
|
onUpdateMessage: (id: string, updates: Partial<Message>) => void;
|
||||||
onStatusChange: (conversationId: string, status: ConversationStatus) => void;
|
onStatusChange: (conversationId: string, status: ConversationStatus) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +70,7 @@ export function MessageThread({
|
|||||||
messages,
|
messages,
|
||||||
onMessagesLoaded,
|
onMessagesLoaded,
|
||||||
onNewMessage,
|
onNewMessage,
|
||||||
|
onUpdateMessage,
|
||||||
onStatusChange,
|
onStatusChange,
|
||||||
}: MessageThreadProps) {
|
}: MessageThreadProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -169,9 +172,11 @@ export function MessageThread({
|
|||||||
async (text: string) => {
|
async (text: string) => {
|
||||||
if (!conversation) return;
|
if (!conversation) return;
|
||||||
|
|
||||||
// Optimistic update
|
const tempId = `temp-${Date.now()}`;
|
||||||
|
|
||||||
|
// Optimistic update — shows the message immediately with "sending" status
|
||||||
const optimisticMsg: Message = {
|
const optimisticMsg: Message = {
|
||||||
id: `temp-${Date.now()}`,
|
id: tempId,
|
||||||
conversation_id: conversation.id,
|
conversation_id: conversation.id,
|
||||||
sender_type: "agent",
|
sender_type: "agent",
|
||||||
content_type: "text",
|
content_type: "text",
|
||||||
@ -192,14 +197,29 @@ export function MessageThread({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const payload = await res.json().catch(() => ({}));
|
||||||
|
|
||||||
if (!res.ok) {
|
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) {
|
} catch (err) {
|
||||||
console.error("Failed to send message:", 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(
|
const handleStatusChange = useCallback(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user