- Build complete Next.js CRM for commercial real estate - Add authentication with JWT sessions and role-based access - Add GoHighLevel API integration for contacts, conversations, opportunities - Add AI-powered Control Center with tool calling - Add Setup page with onboarding checklist (/setup) - Add sidebar navigation with Setup menu item - Fix type errors in onboarding API, GHL services, and control center tools - Add Prisma schema with SQLite for local development - Add UI components with clay morphism design system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
184 lines
5.8 KiB
TypeScript
184 lines
5.8 KiB
TypeScript
import { GHLClient } from '../client';
|
|
import {
|
|
GHLConversation,
|
|
GHLMessage,
|
|
SendMessageDTO,
|
|
GHLPaginatedResponse
|
|
} from '@/types/ghl';
|
|
|
|
export interface ConversationSearchParams {
|
|
locationId?: string;
|
|
contactId?: string;
|
|
limit?: number;
|
|
startAfterId?: string;
|
|
status?: 'all' | 'read' | 'unread' | 'starred';
|
|
}
|
|
|
|
export interface SendSMSParams {
|
|
contactId: string;
|
|
message: string;
|
|
fromNumber?: string;
|
|
}
|
|
|
|
export interface SendEmailParams {
|
|
contactId: string;
|
|
subject: string;
|
|
htmlBody: string;
|
|
fromEmail?: string;
|
|
fromName?: string;
|
|
replyTo?: string;
|
|
cc?: string[];
|
|
bcc?: string[];
|
|
attachments?: { url: string; name: string }[];
|
|
}
|
|
|
|
export class ConversationsService {
|
|
constructor(private client: GHLClient) {}
|
|
|
|
// Get all conversations
|
|
async getAll(params?: ConversationSearchParams): Promise<GHLPaginatedResponse<GHLConversation>> {
|
|
const searchParams: Record<string, string> = {
|
|
locationId: params?.locationId || this.client.locationID,
|
|
};
|
|
|
|
if (params?.limit) searchParams.limit = String(params.limit);
|
|
if (params?.startAfterId) searchParams.startAfterId = params.startAfterId;
|
|
if (params?.contactId) searchParams.contactId = params.contactId;
|
|
if (params?.status) searchParams.status = params.status;
|
|
|
|
// v2 API returns { conversations: [...] } - normalize to { data: [...] }
|
|
const response = await this.client.get<{ conversations: GHLConversation[]; total?: number }>('/conversations/search', searchParams);
|
|
|
|
return {
|
|
data: response.conversations || [],
|
|
meta: { total: response.total || response.conversations?.length || 0, currentPage: 1 },
|
|
};
|
|
}
|
|
|
|
// Get a single conversation by ID
|
|
async getById(conversationId: string): Promise<GHLConversation> {
|
|
return this.client.get(`/conversations/${conversationId}`);
|
|
}
|
|
|
|
// Get conversation by contact ID
|
|
async getByContactId(contactId: string): Promise<GHLConversation | null> {
|
|
const result = await this.getAll({ contactId, limit: 1 });
|
|
return result.data?.[0] || null;
|
|
}
|
|
|
|
// Get messages in a conversation
|
|
async getMessages(conversationId: string, params?: {
|
|
limit?: number;
|
|
startAfterId?: string;
|
|
}): Promise<GHLPaginatedResponse<GHLMessage>> {
|
|
const searchParams: Record<string, string> = {};
|
|
if (params?.limit) searchParams.limit = String(params.limit);
|
|
if (params?.startAfterId) searchParams.startAfterId = params.startAfterId;
|
|
|
|
// v2 API returns { messages: [...] } - normalize to { data: [...] }
|
|
const response = await this.client.get<{ messages: GHLMessage[]; total?: number }>(`/conversations/${conversationId}/messages`, searchParams);
|
|
|
|
return {
|
|
data: response.messages || [],
|
|
meta: { total: response.total || response.messages?.length || 0, currentPage: 1 },
|
|
};
|
|
}
|
|
|
|
// Send an SMS message
|
|
async sendSMS(params: SendSMSParams): Promise<GHLMessage> {
|
|
return this.client.post('/conversations/messages', {
|
|
type: 'SMS',
|
|
contactId: params.contactId,
|
|
message: params.message,
|
|
...(params.fromNumber && { fromNumber: params.fromNumber }),
|
|
});
|
|
}
|
|
|
|
// Send an email message
|
|
async sendEmail(params: SendEmailParams): Promise<GHLMessage> {
|
|
return this.client.post('/conversations/messages', {
|
|
type: 'Email',
|
|
contactId: params.contactId,
|
|
subject: params.subject,
|
|
html: params.htmlBody,
|
|
...(params.fromEmail && { from: params.fromEmail }),
|
|
...(params.fromName && { fromName: params.fromName }),
|
|
...(params.replyTo && { replyTo: params.replyTo }),
|
|
...(params.cc?.length && { cc: params.cc }),
|
|
...(params.bcc?.length && { bcc: params.bcc }),
|
|
...(params.attachments?.length && { attachments: params.attachments }),
|
|
});
|
|
}
|
|
|
|
// Generic send message (SMS or Email)
|
|
async send(params: SendMessageDTO): Promise<GHLMessage> {
|
|
if (params.type === 'SMS') {
|
|
return this.sendSMS({
|
|
contactId: params.contactId,
|
|
message: params.message || '',
|
|
});
|
|
} else {
|
|
return this.sendEmail({
|
|
contactId: params.contactId,
|
|
subject: params.subject || '',
|
|
htmlBody: params.message || '',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mark conversation as read
|
|
async markAsRead(conversationId: string): Promise<void> {
|
|
await this.client.put(`/conversations/${conversationId}/status`, {
|
|
status: 'read',
|
|
});
|
|
}
|
|
|
|
// Mark conversation as unread
|
|
async markAsUnread(conversationId: string): Promise<void> {
|
|
await this.client.put(`/conversations/${conversationId}/status`, {
|
|
status: 'unread',
|
|
});
|
|
}
|
|
|
|
// Star a conversation
|
|
async star(conversationId: string): Promise<void> {
|
|
await this.client.put(`/conversations/${conversationId}/status`, {
|
|
starred: true,
|
|
});
|
|
}
|
|
|
|
// Unstar a conversation
|
|
async unstar(conversationId: string): Promise<void> {
|
|
await this.client.put(`/conversations/${conversationId}/status`, {
|
|
starred: false,
|
|
});
|
|
}
|
|
|
|
// Delete a conversation (archive)
|
|
async delete(conversationId: string): Promise<void> {
|
|
await this.client.delete(`/conversations/${conversationId}`);
|
|
}
|
|
|
|
// Get unread count
|
|
async getUnreadCount(): Promise<number> {
|
|
const result = await this.getAll({ status: 'unread', limit: 1 });
|
|
return result.meta?.total || 0;
|
|
}
|
|
|
|
// Schedule a message (if supported)
|
|
async scheduleMessage(params: SendMessageDTO & { scheduledAt: Date }): Promise<GHLMessage> {
|
|
return this.client.post('/conversations/messages', {
|
|
type: params.type,
|
|
contactId: params.contactId,
|
|
message: params.message,
|
|
...(params.subject && { subject: params.subject }),
|
|
scheduledTimestamp: params.scheduledAt.toISOString(),
|
|
});
|
|
}
|
|
|
|
// Cancel a scheduled message
|
|
async cancelScheduledMessage(messageId: string): Promise<void> {
|
|
await this.client.delete(`/conversations/messages/${messageId}/schedule`);
|
|
}
|
|
}
|