@greatapps/greatchat-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +138 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/client/index.ts +191 -0
- package/src/index.ts +18 -0
- package/src/lib/utils.ts +6 -0
- package/src/types/index.ts +117 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
interface ApiResponse<T> {
|
|
4
|
+
status: 0 | 1;
|
|
5
|
+
message: string;
|
|
6
|
+
data: T;
|
|
7
|
+
total?: number;
|
|
8
|
+
}
|
|
9
|
+
interface Channel {
|
|
10
|
+
id: number;
|
|
11
|
+
id_account: number;
|
|
12
|
+
name: string;
|
|
13
|
+
type: "whatsapp_unofficial";
|
|
14
|
+
provider: "gwhatsmeow";
|
|
15
|
+
identifier: string | null;
|
|
16
|
+
external_id: string | null;
|
|
17
|
+
config: string | null;
|
|
18
|
+
status: "active" | "inactive";
|
|
19
|
+
id_agent: number | null;
|
|
20
|
+
deleted: number;
|
|
21
|
+
datetime_add: string;
|
|
22
|
+
datetime_alt: string | null;
|
|
23
|
+
}
|
|
24
|
+
interface Contact {
|
|
25
|
+
id: number;
|
|
26
|
+
id_account: number;
|
|
27
|
+
name: string;
|
|
28
|
+
phone_number: string | null;
|
|
29
|
+
identifier: string | null;
|
|
30
|
+
metadata: string | null;
|
|
31
|
+
deleted: number;
|
|
32
|
+
datetime_add: string;
|
|
33
|
+
datetime_alt: string | null;
|
|
34
|
+
}
|
|
35
|
+
interface Inbox {
|
|
36
|
+
id: number;
|
|
37
|
+
id_account: number;
|
|
38
|
+
id_channel: number;
|
|
39
|
+
id_contact: number;
|
|
40
|
+
id_agent: number | null;
|
|
41
|
+
status: "open" | "pending" | "resolved";
|
|
42
|
+
last_message_at: string | null;
|
|
43
|
+
external_id: string | null;
|
|
44
|
+
contact_name: string | null;
|
|
45
|
+
contact_phone: string | null;
|
|
46
|
+
contact_identifier: string | null;
|
|
47
|
+
channel_name: string | null;
|
|
48
|
+
channel_identifier: string | null;
|
|
49
|
+
channel_type: string | null;
|
|
50
|
+
last_message_content: string | null;
|
|
51
|
+
last_message_content_type: string | null;
|
|
52
|
+
last_message_direction: "inbound" | "outbound" | null;
|
|
53
|
+
last_message_source: "contact" | "agent" | "bot" | null;
|
|
54
|
+
datetime_add: string;
|
|
55
|
+
datetime_alt: string | null;
|
|
56
|
+
}
|
|
57
|
+
type MessageContentType = "text" | "image" | "audio" | "video" | "document" | "location" | "contacts";
|
|
58
|
+
interface InboxMessage {
|
|
59
|
+
id: number;
|
|
60
|
+
id_account: number;
|
|
61
|
+
id_inbox: number;
|
|
62
|
+
id_contact: number | null;
|
|
63
|
+
direction: "inbound" | "outbound";
|
|
64
|
+
content: string | null;
|
|
65
|
+
content_type: MessageContentType;
|
|
66
|
+
content_url: string | null;
|
|
67
|
+
metadata: string | null;
|
|
68
|
+
external_id: string | null;
|
|
69
|
+
status: "sent" | "delivered" | "read" | "failed" | "received" | "pending";
|
|
70
|
+
source: "contact" | "agent" | "bot";
|
|
71
|
+
is_private: boolean;
|
|
72
|
+
datetime_add: string;
|
|
73
|
+
datetime_alt: string | null;
|
|
74
|
+
/** UI-only: error message when status is "failed" */
|
|
75
|
+
_error?: string;
|
|
76
|
+
}
|
|
77
|
+
interface InboxStats {
|
|
78
|
+
total: number;
|
|
79
|
+
open: number;
|
|
80
|
+
pending: number;
|
|
81
|
+
resolved: number;
|
|
82
|
+
}
|
|
83
|
+
interface WhatsappStatus {
|
|
84
|
+
status: string;
|
|
85
|
+
connected: boolean;
|
|
86
|
+
logged_in: boolean;
|
|
87
|
+
jid?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface GchatClientConfig {
|
|
91
|
+
baseUrl: string;
|
|
92
|
+
token: string;
|
|
93
|
+
language?: string;
|
|
94
|
+
idWl?: number;
|
|
95
|
+
}
|
|
96
|
+
declare function createGchatClient(config: GchatClientConfig): {
|
|
97
|
+
listInboxes: (idAccount: number, params?: Record<string, string>) => Promise<ApiResponse<Inbox[]>>;
|
|
98
|
+
getInbox: (idAccount: number, id: number) => Promise<ApiResponse<Inbox>>;
|
|
99
|
+
getInboxStats: (idAccount: number) => Promise<ApiResponse<InboxStats>>;
|
|
100
|
+
createInbox: (idAccount: number, body: {
|
|
101
|
+
id_channel: number;
|
|
102
|
+
id_contact: number;
|
|
103
|
+
}) => Promise<ApiResponse<Inbox>>;
|
|
104
|
+
updateInbox: (idAccount: number, id: number, body: Partial<Pick<Inbox, "status" | "id_agent">>) => Promise<ApiResponse<Inbox>>;
|
|
105
|
+
deleteInbox: (idAccount: number, id: number) => Promise<ApiResponse<void>>;
|
|
106
|
+
listInboxMessages: (idAccount: number, params: Record<string, string>) => Promise<ApiResponse<InboxMessage[]>>;
|
|
107
|
+
sendMessage: (idAccount: number, body: {
|
|
108
|
+
id_inbox: number;
|
|
109
|
+
content: string;
|
|
110
|
+
content_type?: string;
|
|
111
|
+
source?: string;
|
|
112
|
+
direction: "outbound";
|
|
113
|
+
}) => Promise<ApiResponse<InboxMessage>>;
|
|
114
|
+
revokeMessage: (idAccount: number, id: number) => Promise<ApiResponse<void>>;
|
|
115
|
+
editMessage: (idAccount: number, id: number, body: {
|
|
116
|
+
content: string;
|
|
117
|
+
}) => Promise<ApiResponse<void>>;
|
|
118
|
+
listContacts: (idAccount: number, params?: Record<string, string>) => Promise<ApiResponse<Contact[]>>;
|
|
119
|
+
getContact: (idAccount: number, id: number) => Promise<ApiResponse<Contact>>;
|
|
120
|
+
createContact: (idAccount: number, body: Pick<Contact, "name"> & Partial<Pick<Contact, "phone_number" | "identifier">>) => Promise<ApiResponse<Contact>>;
|
|
121
|
+
updateContact: (idAccount: number, id: number, body: Partial<Pick<Contact, "name" | "phone_number" | "identifier">>) => Promise<ApiResponse<Contact>>;
|
|
122
|
+
deleteContact: (idAccount: number, id: number) => Promise<ApiResponse<void>>;
|
|
123
|
+
listChannels: (idAccount: number) => Promise<ApiResponse<Channel[]>>;
|
|
124
|
+
getChannel: (idAccount: number, id: number) => Promise<ApiResponse<Channel>>;
|
|
125
|
+
createChannel: (idAccount: number, body: Pick<Channel, "name" | "type" | "provider"> & Partial<Pick<Channel, "identifier" | "id_agent">>) => Promise<ApiResponse<Channel>>;
|
|
126
|
+
updateChannel: (idAccount: number, id: number, body: Partial<Pick<Channel, "name" | "identifier" | "status" | "id_agent">>) => Promise<ApiResponse<Channel>>;
|
|
127
|
+
deleteChannel: (idAccount: number, id: number) => Promise<ApiResponse<void>>;
|
|
128
|
+
connectChannel: (idAccount: number, id: number) => Promise<ApiResponse<unknown>>;
|
|
129
|
+
getChannelQR: (idAccount: number, id: number) => Promise<string | null>;
|
|
130
|
+
getChannelWhatsappStatus: (idAccount: number, id: number) => Promise<ApiResponse<WhatsappStatus>>;
|
|
131
|
+
disconnectChannel: (idAccount: number, id: number) => Promise<ApiResponse<unknown>>;
|
|
132
|
+
logoutChannel: (idAccount: number, id: number) => Promise<ApiResponse<unknown>>;
|
|
133
|
+
provisionChannel: (idAccount: number, id: number) => Promise<ApiResponse<unknown>>;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
137
|
+
|
|
138
|
+
export { type ApiResponse, type Channel, type Contact, type GchatClientConfig, type Inbox, type InboxMessage, type InboxStats, type MessageContentType, type WhatsappStatus, cn, createGchatClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// src/client/index.ts
|
|
2
|
+
function createGchatClient(config) {
|
|
3
|
+
const { baseUrl, token, language = "pt-br", idWl = 1 } = config;
|
|
4
|
+
function buildUrl(idAccount, path) {
|
|
5
|
+
return `${baseUrl}/v1/${language}/${idWl}/accounts/${idAccount}/${path}`;
|
|
6
|
+
}
|
|
7
|
+
async function request(method, idAccount, path, body, params) {
|
|
8
|
+
const url = new URL(buildUrl(idAccount, path));
|
|
9
|
+
if (params) {
|
|
10
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
11
|
+
}
|
|
12
|
+
const res = await fetch(url.toString(), {
|
|
13
|
+
method,
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
Authorization: `Bearer ${token}`
|
|
17
|
+
},
|
|
18
|
+
body: body ? JSON.stringify(body) : void 0
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
let message = `HTTP ${res.status}: ${res.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await res.json();
|
|
24
|
+
message = errorBody.message || message;
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
throw new Error(message);
|
|
28
|
+
}
|
|
29
|
+
const json = await res.json();
|
|
30
|
+
if (method !== "GET" && json.status === 0) {
|
|
31
|
+
throw new Error(json.message || "Opera\xE7\xE3o falhou");
|
|
32
|
+
}
|
|
33
|
+
return json;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
// --- Inboxes ---
|
|
37
|
+
listInboxes: (idAccount, params) => request("GET", idAccount, "inboxes", void 0, params),
|
|
38
|
+
getInbox: (idAccount, id) => request("GET", idAccount, `inboxes/${id}`),
|
|
39
|
+
getInboxStats: (idAccount) => request("GET", idAccount, "inboxes/stats"),
|
|
40
|
+
createInbox: (idAccount, body) => request("POST", idAccount, "inboxes", body),
|
|
41
|
+
updateInbox: (idAccount, id, body) => request("PUT", idAccount, `inboxes/${id}`, body),
|
|
42
|
+
deleteInbox: (idAccount, id) => request("DELETE", idAccount, `inboxes/${id}`),
|
|
43
|
+
// --- Inbox Messages ---
|
|
44
|
+
listInboxMessages: (idAccount, params) => request(
|
|
45
|
+
"GET",
|
|
46
|
+
idAccount,
|
|
47
|
+
"inbox-messages",
|
|
48
|
+
void 0,
|
|
49
|
+
params
|
|
50
|
+
),
|
|
51
|
+
sendMessage: (idAccount, body) => request("POST", idAccount, "inbox-messages", body),
|
|
52
|
+
revokeMessage: (idAccount, id) => request("POST", idAccount, `inbox-messages/${id}/revoke`),
|
|
53
|
+
editMessage: (idAccount, id, body) => request("POST", idAccount, `inbox-messages/${id}/edit`, body),
|
|
54
|
+
// --- Contacts ---
|
|
55
|
+
listContacts: (idAccount, params) => request("GET", idAccount, "contacts", void 0, params),
|
|
56
|
+
getContact: (idAccount, id) => request("GET", idAccount, `contacts/${id}`),
|
|
57
|
+
createContact: (idAccount, body) => request("POST", idAccount, "contacts", body),
|
|
58
|
+
updateContact: (idAccount, id, body) => request("PUT", idAccount, `contacts/${id}`, body),
|
|
59
|
+
deleteContact: (idAccount, id) => request("DELETE", idAccount, `contacts/${id}`),
|
|
60
|
+
// --- Channels ---
|
|
61
|
+
listChannels: (idAccount) => request("GET", idAccount, "channels"),
|
|
62
|
+
getChannel: (idAccount, id) => request("GET", idAccount, `channels/${id}`),
|
|
63
|
+
createChannel: (idAccount, body) => request("POST", idAccount, "channels", body),
|
|
64
|
+
updateChannel: (idAccount, id, body) => request("PUT", idAccount, `channels/${id}`, body),
|
|
65
|
+
deleteChannel: (idAccount, id) => request("DELETE", idAccount, `channels/${id}`),
|
|
66
|
+
// --- WhatsApp Channel Management ---
|
|
67
|
+
connectChannel: (idAccount, id) => request("POST", idAccount, `channels/${id}/connect`),
|
|
68
|
+
getChannelQR: async (idAccount, id) => {
|
|
69
|
+
const url = buildUrl(idAccount, `channels/${id}/qr`);
|
|
70
|
+
const res = await fetch(url, {
|
|
71
|
+
headers: {
|
|
72
|
+
Accept: "image/png",
|
|
73
|
+
Authorization: `Bearer ${token}`
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) return null;
|
|
77
|
+
const contentType = res.headers.get("content-type") || "";
|
|
78
|
+
if (!contentType.includes("image/")) return null;
|
|
79
|
+
const blob = await res.blob();
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const reader = new FileReader();
|
|
82
|
+
reader.onloadend = () => resolve(reader.result);
|
|
83
|
+
reader.onerror = () => reject(new Error("Failed to read QR image"));
|
|
84
|
+
reader.readAsDataURL(blob);
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
getChannelWhatsappStatus: (idAccount, id) => request(
|
|
88
|
+
"GET",
|
|
89
|
+
idAccount,
|
|
90
|
+
`channels/${id}/whatsapp-status`
|
|
91
|
+
),
|
|
92
|
+
disconnectChannel: (idAccount, id) => request("POST", idAccount, `channels/${id}/disconnect`),
|
|
93
|
+
logoutChannel: (idAccount, id) => request("POST", idAccount, `channels/${id}/logout`),
|
|
94
|
+
provisionChannel: (idAccount, id) => request("POST", idAccount, `channels/${id}/provision`)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/lib/utils.ts
|
|
99
|
+
import { clsx } from "clsx";
|
|
100
|
+
import { twMerge } from "tailwind-merge";
|
|
101
|
+
function cn(...inputs) {
|
|
102
|
+
return twMerge(clsx(inputs));
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
cn,
|
|
106
|
+
createGchatClient
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/index.ts","../src/lib/utils.ts"],"sourcesContent":["import type {\n ApiResponse,\n Channel,\n Contact,\n Inbox,\n InboxMessage,\n InboxStats,\n WhatsappStatus,\n} from \"../types\";\n\nexport interface GchatClientConfig {\n baseUrl: string;\n token: string;\n language?: string;\n idWl?: number;\n}\n\nexport function createGchatClient(config: GchatClientConfig) {\n const { baseUrl, token, language = \"pt-br\", idWl = 1 } = config;\n\n function buildUrl(idAccount: number, path: string): string {\n return `${baseUrl}/v1/${language}/${idWl}/accounts/${idAccount}/${path}`;\n }\n\n async function request<T>(\n method: string,\n idAccount: number,\n path: string,\n body?: unknown,\n params?: Record<string, string>,\n ): Promise<ApiResponse<T>> {\n const url = new URL(buildUrl(idAccount, path));\n if (params) {\n Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));\n }\n\n const res = await fetch(url.toString(), {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!res.ok) {\n let message = `HTTP ${res.status}: ${res.statusText}`;\n try {\n const errorBody = await res.json();\n message = errorBody.message || message;\n } catch {\n /* ignore parse errors */\n }\n throw new Error(message);\n }\n\n const json: ApiResponse<T> = await res.json();\n\n // For mutations, treat API-level failures as errors so TanStack Query\n // fires onError instead of onSuccess (API returns HTTP 200 even on failure)\n if (method !== \"GET\" && json.status === 0) {\n throw new Error(json.message || \"Operação falhou\");\n }\n\n return json;\n }\n\n return {\n // --- Inboxes ---\n listInboxes: (idAccount: number, params?: Record<string, string>) =>\n request<Inbox[]>(\"GET\", idAccount, \"inboxes\", undefined, params),\n getInbox: (idAccount: number, id: number) =>\n request<Inbox>(\"GET\", idAccount, `inboxes/${id}`),\n getInboxStats: (idAccount: number) =>\n request<InboxStats>(\"GET\", idAccount, \"inboxes/stats\"),\n createInbox: (\n idAccount: number,\n body: { id_channel: number; id_contact: number },\n ) => request<Inbox>(\"POST\", idAccount, \"inboxes\", body),\n updateInbox: (\n idAccount: number,\n id: number,\n body: Partial<Pick<Inbox, \"status\" | \"id_agent\">>,\n ) => request<Inbox>(\"PUT\", idAccount, `inboxes/${id}`, body),\n deleteInbox: (idAccount: number, id: number) =>\n request<void>(\"DELETE\", idAccount, `inboxes/${id}`),\n\n // --- Inbox Messages ---\n listInboxMessages: (idAccount: number, params: Record<string, string>) =>\n request<InboxMessage[]>(\n \"GET\",\n idAccount,\n \"inbox-messages\",\n undefined,\n params,\n ),\n sendMessage: (\n idAccount: number,\n body: {\n id_inbox: number;\n content: string;\n content_type?: string;\n source?: string;\n direction: \"outbound\";\n },\n ) => request<InboxMessage>(\"POST\", idAccount, \"inbox-messages\", body),\n revokeMessage: (idAccount: number, id: number) =>\n request<void>(\"POST\", idAccount, `inbox-messages/${id}/revoke`),\n editMessage: (idAccount: number, id: number, body: { content: string }) =>\n request<void>(\"POST\", idAccount, `inbox-messages/${id}/edit`, body),\n\n // --- Contacts ---\n listContacts: (idAccount: number, params?: Record<string, string>) =>\n request<Contact[]>(\"GET\", idAccount, \"contacts\", undefined, params),\n getContact: (idAccount: number, id: number) =>\n request<Contact>(\"GET\", idAccount, `contacts/${id}`),\n createContact: (\n idAccount: number,\n body: Pick<Contact, \"name\"> &\n Partial<Pick<Contact, \"phone_number\" | \"identifier\">>,\n ) => request<Contact>(\"POST\", idAccount, \"contacts\", body),\n updateContact: (\n idAccount: number,\n id: number,\n body: Partial<Pick<Contact, \"name\" | \"phone_number\" | \"identifier\">>,\n ) => request<Contact>(\"PUT\", idAccount, `contacts/${id}`, body),\n deleteContact: (idAccount: number, id: number) =>\n request<void>(\"DELETE\", idAccount, `contacts/${id}`),\n\n // --- Channels ---\n listChannels: (idAccount: number) =>\n request<Channel[]>(\"GET\", idAccount, \"channels\"),\n getChannel: (idAccount: number, id: number) =>\n request<Channel>(\"GET\", idAccount, `channels/${id}`),\n createChannel: (\n idAccount: number,\n body: Pick<Channel, \"name\" | \"type\" | \"provider\"> &\n Partial<Pick<Channel, \"identifier\" | \"id_agent\">>,\n ) => request<Channel>(\"POST\", idAccount, \"channels\", body),\n updateChannel: (\n idAccount: number,\n id: number,\n body: Partial<\n Pick<Channel, \"name\" | \"identifier\" | \"status\" | \"id_agent\">\n >,\n ) => request<Channel>(\"PUT\", idAccount, `channels/${id}`, body),\n deleteChannel: (idAccount: number, id: number) =>\n request<void>(\"DELETE\", idAccount, `channels/${id}`),\n\n // --- WhatsApp Channel Management ---\n connectChannel: (idAccount: number, id: number) =>\n request<unknown>(\"POST\", idAccount, `channels/${id}/connect`),\n getChannelQR: async (\n idAccount: number,\n id: number,\n ): Promise<string | null> => {\n const url = buildUrl(idAccount, `channels/${id}/qr`);\n const res = await fetch(url, {\n headers: {\n Accept: \"image/png\",\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!res.ok) return null;\n\n const contentType = res.headers.get(\"content-type\") || \"\";\n if (!contentType.includes(\"image/\")) return null;\n\n const blob = await res.blob();\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result as string);\n reader.onerror = () => reject(new Error(\"Failed to read QR image\"));\n reader.readAsDataURL(blob);\n });\n },\n getChannelWhatsappStatus: (idAccount: number, id: number) =>\n request<WhatsappStatus>(\n \"GET\",\n idAccount,\n `channels/${id}/whatsapp-status`,\n ),\n disconnectChannel: (idAccount: number, id: number) =>\n request<unknown>(\"POST\", idAccount, `channels/${id}/disconnect`),\n logoutChannel: (idAccount: number, id: number) =>\n request<unknown>(\"POST\", idAccount, `channels/${id}/logout`),\n provisionChannel: (idAccount: number, id: number) =>\n request<unknown>(\"POST\", idAccount, `channels/${id}/provision`),\n };\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@greatapps/greatchat-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": ["dist", "src"],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "tsup --watch"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": "^19",
|
|
20
|
+
"react-dom": "^19",
|
|
21
|
+
"@tanstack/react-query": "^5",
|
|
22
|
+
"lucide-react": "*",
|
|
23
|
+
"date-fns": "*",
|
|
24
|
+
"sonner": "*"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"clsx": "^2",
|
|
28
|
+
"tailwind-merge": "^3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsup": "latest",
|
|
32
|
+
"typescript": "^5",
|
|
33
|
+
"react": "^19",
|
|
34
|
+
"react-dom": "^19",
|
|
35
|
+
"@types/react": "latest",
|
|
36
|
+
"@types/react-dom": "latest",
|
|
37
|
+
"@tanstack/react-query": "latest"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApiResponse,
|
|
3
|
+
Channel,
|
|
4
|
+
Contact,
|
|
5
|
+
Inbox,
|
|
6
|
+
InboxMessage,
|
|
7
|
+
InboxStats,
|
|
8
|
+
WhatsappStatus,
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
export interface GchatClientConfig {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
token: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
idWl?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createGchatClient(config: GchatClientConfig) {
|
|
19
|
+
const { baseUrl, token, language = "pt-br", idWl = 1 } = config;
|
|
20
|
+
|
|
21
|
+
function buildUrl(idAccount: number, path: string): string {
|
|
22
|
+
return `${baseUrl}/v1/${language}/${idWl}/accounts/${idAccount}/${path}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function request<T>(
|
|
26
|
+
method: string,
|
|
27
|
+
idAccount: number,
|
|
28
|
+
path: string,
|
|
29
|
+
body?: unknown,
|
|
30
|
+
params?: Record<string, string>,
|
|
31
|
+
): Promise<ApiResponse<T>> {
|
|
32
|
+
const url = new URL(buildUrl(idAccount, path));
|
|
33
|
+
if (params) {
|
|
34
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const res = await fetch(url.toString(), {
|
|
38
|
+
method,
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
Authorization: `Bearer ${token}`,
|
|
42
|
+
},
|
|
43
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
let message = `HTTP ${res.status}: ${res.statusText}`;
|
|
48
|
+
try {
|
|
49
|
+
const errorBody = await res.json();
|
|
50
|
+
message = errorBody.message || message;
|
|
51
|
+
} catch {
|
|
52
|
+
/* ignore parse errors */
|
|
53
|
+
}
|
|
54
|
+
throw new Error(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const json: ApiResponse<T> = await res.json();
|
|
58
|
+
|
|
59
|
+
// For mutations, treat API-level failures as errors so TanStack Query
|
|
60
|
+
// fires onError instead of onSuccess (API returns HTTP 200 even on failure)
|
|
61
|
+
if (method !== "GET" && json.status === 0) {
|
|
62
|
+
throw new Error(json.message || "Operação falhou");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return json;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
// --- Inboxes ---
|
|
70
|
+
listInboxes: (idAccount: number, params?: Record<string, string>) =>
|
|
71
|
+
request<Inbox[]>("GET", idAccount, "inboxes", undefined, params),
|
|
72
|
+
getInbox: (idAccount: number, id: number) =>
|
|
73
|
+
request<Inbox>("GET", idAccount, `inboxes/${id}`),
|
|
74
|
+
getInboxStats: (idAccount: number) =>
|
|
75
|
+
request<InboxStats>("GET", idAccount, "inboxes/stats"),
|
|
76
|
+
createInbox: (
|
|
77
|
+
idAccount: number,
|
|
78
|
+
body: { id_channel: number; id_contact: number },
|
|
79
|
+
) => request<Inbox>("POST", idAccount, "inboxes", body),
|
|
80
|
+
updateInbox: (
|
|
81
|
+
idAccount: number,
|
|
82
|
+
id: number,
|
|
83
|
+
body: Partial<Pick<Inbox, "status" | "id_agent">>,
|
|
84
|
+
) => request<Inbox>("PUT", idAccount, `inboxes/${id}`, body),
|
|
85
|
+
deleteInbox: (idAccount: number, id: number) =>
|
|
86
|
+
request<void>("DELETE", idAccount, `inboxes/${id}`),
|
|
87
|
+
|
|
88
|
+
// --- Inbox Messages ---
|
|
89
|
+
listInboxMessages: (idAccount: number, params: Record<string, string>) =>
|
|
90
|
+
request<InboxMessage[]>(
|
|
91
|
+
"GET",
|
|
92
|
+
idAccount,
|
|
93
|
+
"inbox-messages",
|
|
94
|
+
undefined,
|
|
95
|
+
params,
|
|
96
|
+
),
|
|
97
|
+
sendMessage: (
|
|
98
|
+
idAccount: number,
|
|
99
|
+
body: {
|
|
100
|
+
id_inbox: number;
|
|
101
|
+
content: string;
|
|
102
|
+
content_type?: string;
|
|
103
|
+
source?: string;
|
|
104
|
+
direction: "outbound";
|
|
105
|
+
},
|
|
106
|
+
) => request<InboxMessage>("POST", idAccount, "inbox-messages", body),
|
|
107
|
+
revokeMessage: (idAccount: number, id: number) =>
|
|
108
|
+
request<void>("POST", idAccount, `inbox-messages/${id}/revoke`),
|
|
109
|
+
editMessage: (idAccount: number, id: number, body: { content: string }) =>
|
|
110
|
+
request<void>("POST", idAccount, `inbox-messages/${id}/edit`, body),
|
|
111
|
+
|
|
112
|
+
// --- Contacts ---
|
|
113
|
+
listContacts: (idAccount: number, params?: Record<string, string>) =>
|
|
114
|
+
request<Contact[]>("GET", idAccount, "contacts", undefined, params),
|
|
115
|
+
getContact: (idAccount: number, id: number) =>
|
|
116
|
+
request<Contact>("GET", idAccount, `contacts/${id}`),
|
|
117
|
+
createContact: (
|
|
118
|
+
idAccount: number,
|
|
119
|
+
body: Pick<Contact, "name"> &
|
|
120
|
+
Partial<Pick<Contact, "phone_number" | "identifier">>,
|
|
121
|
+
) => request<Contact>("POST", idAccount, "contacts", body),
|
|
122
|
+
updateContact: (
|
|
123
|
+
idAccount: number,
|
|
124
|
+
id: number,
|
|
125
|
+
body: Partial<Pick<Contact, "name" | "phone_number" | "identifier">>,
|
|
126
|
+
) => request<Contact>("PUT", idAccount, `contacts/${id}`, body),
|
|
127
|
+
deleteContact: (idAccount: number, id: number) =>
|
|
128
|
+
request<void>("DELETE", idAccount, `contacts/${id}`),
|
|
129
|
+
|
|
130
|
+
// --- Channels ---
|
|
131
|
+
listChannels: (idAccount: number) =>
|
|
132
|
+
request<Channel[]>("GET", idAccount, "channels"),
|
|
133
|
+
getChannel: (idAccount: number, id: number) =>
|
|
134
|
+
request<Channel>("GET", idAccount, `channels/${id}`),
|
|
135
|
+
createChannel: (
|
|
136
|
+
idAccount: number,
|
|
137
|
+
body: Pick<Channel, "name" | "type" | "provider"> &
|
|
138
|
+
Partial<Pick<Channel, "identifier" | "id_agent">>,
|
|
139
|
+
) => request<Channel>("POST", idAccount, "channels", body),
|
|
140
|
+
updateChannel: (
|
|
141
|
+
idAccount: number,
|
|
142
|
+
id: number,
|
|
143
|
+
body: Partial<
|
|
144
|
+
Pick<Channel, "name" | "identifier" | "status" | "id_agent">
|
|
145
|
+
>,
|
|
146
|
+
) => request<Channel>("PUT", idAccount, `channels/${id}`, body),
|
|
147
|
+
deleteChannel: (idAccount: number, id: number) =>
|
|
148
|
+
request<void>("DELETE", idAccount, `channels/${id}`),
|
|
149
|
+
|
|
150
|
+
// --- WhatsApp Channel Management ---
|
|
151
|
+
connectChannel: (idAccount: number, id: number) =>
|
|
152
|
+
request<unknown>("POST", idAccount, `channels/${id}/connect`),
|
|
153
|
+
getChannelQR: async (
|
|
154
|
+
idAccount: number,
|
|
155
|
+
id: number,
|
|
156
|
+
): Promise<string | null> => {
|
|
157
|
+
const url = buildUrl(idAccount, `channels/${id}/qr`);
|
|
158
|
+
const res = await fetch(url, {
|
|
159
|
+
headers: {
|
|
160
|
+
Accept: "image/png",
|
|
161
|
+
Authorization: `Bearer ${token}`,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (!res.ok) return null;
|
|
166
|
+
|
|
167
|
+
const contentType = res.headers.get("content-type") || "";
|
|
168
|
+
if (!contentType.includes("image/")) return null;
|
|
169
|
+
|
|
170
|
+
const blob = await res.blob();
|
|
171
|
+
return new Promise<string>((resolve, reject) => {
|
|
172
|
+
const reader = new FileReader();
|
|
173
|
+
reader.onloadend = () => resolve(reader.result as string);
|
|
174
|
+
reader.onerror = () => reject(new Error("Failed to read QR image"));
|
|
175
|
+
reader.readAsDataURL(blob);
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
getChannelWhatsappStatus: (idAccount: number, id: number) =>
|
|
179
|
+
request<WhatsappStatus>(
|
|
180
|
+
"GET",
|
|
181
|
+
idAccount,
|
|
182
|
+
`channels/${id}/whatsapp-status`,
|
|
183
|
+
),
|
|
184
|
+
disconnectChannel: (idAccount: number, id: number) =>
|
|
185
|
+
request<unknown>("POST", idAccount, `channels/${id}/disconnect`),
|
|
186
|
+
logoutChannel: (idAccount: number, id: number) =>
|
|
187
|
+
request<unknown>("POST", idAccount, `channels/${id}/logout`),
|
|
188
|
+
provisionChannel: (idAccount: number, id: number) =>
|
|
189
|
+
request<unknown>("POST", idAccount, `channels/${id}/provision`),
|
|
190
|
+
};
|
|
191
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type {
|
|
3
|
+
ApiResponse,
|
|
4
|
+
Channel,
|
|
5
|
+
Contact,
|
|
6
|
+
Inbox,
|
|
7
|
+
InboxMessage,
|
|
8
|
+
InboxStats,
|
|
9
|
+
WhatsappStatus,
|
|
10
|
+
MessageContentType,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
// Client
|
|
14
|
+
export { createGchatClient } from "./client";
|
|
15
|
+
export type { GchatClientConfig } from "./client";
|
|
16
|
+
|
|
17
|
+
// Utils
|
|
18
|
+
export { cn } from "./lib/utils";
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// === API Response ===
|
|
2
|
+
|
|
3
|
+
export interface ApiResponse<T> {
|
|
4
|
+
status: 0 | 1;
|
|
5
|
+
message: string;
|
|
6
|
+
data: T;
|
|
7
|
+
total?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// === Channel ===
|
|
11
|
+
|
|
12
|
+
export interface Channel {
|
|
13
|
+
id: number;
|
|
14
|
+
id_account: number;
|
|
15
|
+
name: string;
|
|
16
|
+
type: "whatsapp_unofficial";
|
|
17
|
+
provider: "gwhatsmeow";
|
|
18
|
+
identifier: string | null;
|
|
19
|
+
external_id: string | null;
|
|
20
|
+
config: string | null;
|
|
21
|
+
status: "active" | "inactive";
|
|
22
|
+
id_agent: number | null;
|
|
23
|
+
deleted: number;
|
|
24
|
+
datetime_add: string;
|
|
25
|
+
datetime_alt: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// === Contact ===
|
|
29
|
+
|
|
30
|
+
export interface Contact {
|
|
31
|
+
id: number;
|
|
32
|
+
id_account: number;
|
|
33
|
+
name: string;
|
|
34
|
+
phone_number: string | null;
|
|
35
|
+
identifier: string | null;
|
|
36
|
+
metadata: string | null;
|
|
37
|
+
deleted: number;
|
|
38
|
+
datetime_add: string;
|
|
39
|
+
datetime_alt: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// === Inbox ===
|
|
43
|
+
|
|
44
|
+
export interface Inbox {
|
|
45
|
+
id: number;
|
|
46
|
+
id_account: number;
|
|
47
|
+
id_channel: number;
|
|
48
|
+
id_contact: number;
|
|
49
|
+
id_agent: number | null;
|
|
50
|
+
status: "open" | "pending" | "resolved";
|
|
51
|
+
last_message_at: string | null;
|
|
52
|
+
external_id: string | null;
|
|
53
|
+
// Enriched fields (from JOIN queries)
|
|
54
|
+
contact_name: string | null;
|
|
55
|
+
contact_phone: string | null;
|
|
56
|
+
contact_identifier: string | null;
|
|
57
|
+
channel_name: string | null;
|
|
58
|
+
channel_identifier: string | null;
|
|
59
|
+
channel_type: string | null;
|
|
60
|
+
last_message_content: string | null;
|
|
61
|
+
last_message_content_type: string | null;
|
|
62
|
+
last_message_direction: "inbound" | "outbound" | null;
|
|
63
|
+
last_message_source: "contact" | "agent" | "bot" | null;
|
|
64
|
+
datetime_add: string;
|
|
65
|
+
datetime_alt: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// === Message Content Type ===
|
|
69
|
+
|
|
70
|
+
export type MessageContentType =
|
|
71
|
+
| "text"
|
|
72
|
+
| "image"
|
|
73
|
+
| "audio"
|
|
74
|
+
| "video"
|
|
75
|
+
| "document"
|
|
76
|
+
| "location"
|
|
77
|
+
| "contacts";
|
|
78
|
+
|
|
79
|
+
// === Inbox Message ===
|
|
80
|
+
|
|
81
|
+
export interface InboxMessage {
|
|
82
|
+
id: number;
|
|
83
|
+
id_account: number;
|
|
84
|
+
id_inbox: number;
|
|
85
|
+
id_contact: number | null;
|
|
86
|
+
direction: "inbound" | "outbound";
|
|
87
|
+
content: string | null;
|
|
88
|
+
content_type: MessageContentType;
|
|
89
|
+
content_url: string | null;
|
|
90
|
+
metadata: string | null;
|
|
91
|
+
external_id: string | null;
|
|
92
|
+
status: "sent" | "delivered" | "read" | "failed" | "received" | "pending";
|
|
93
|
+
source: "contact" | "agent" | "bot";
|
|
94
|
+
is_private: boolean;
|
|
95
|
+
datetime_add: string;
|
|
96
|
+
datetime_alt: string | null;
|
|
97
|
+
/** UI-only: error message when status is "failed" */
|
|
98
|
+
_error?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// === Inbox Stats ===
|
|
102
|
+
|
|
103
|
+
export interface InboxStats {
|
|
104
|
+
total: number;
|
|
105
|
+
open: number;
|
|
106
|
+
pending: number;
|
|
107
|
+
resolved: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// === WhatsApp Status ===
|
|
111
|
+
|
|
112
|
+
export interface WhatsappStatus {
|
|
113
|
+
status: string;
|
|
114
|
+
connected: boolean;
|
|
115
|
+
logged_in: boolean;
|
|
116
|
+
jid?: string;
|
|
117
|
+
}
|