create-supyagent-app 0.1.14 → 0.1.16
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/package.json
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Users,
|
|
4
|
+
Building2,
|
|
5
|
+
Mail,
|
|
6
|
+
Phone,
|
|
7
|
+
Globe,
|
|
8
|
+
Briefcase,
|
|
9
|
+
DollarSign,
|
|
10
|
+
Calendar,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
/* Interfaces */
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
3
16
|
|
|
4
17
|
interface HubspotContactData {
|
|
5
18
|
id?: string;
|
|
6
|
-
/** Normalized fields returned by Supyagent API */
|
|
7
19
|
firstName?: string;
|
|
8
20
|
lastName?: string;
|
|
9
21
|
email?: string;
|
|
10
22
|
phone?: string;
|
|
11
23
|
company?: string;
|
|
12
|
-
/** Raw HubSpot properties (direct HubSpot API shape) */
|
|
13
24
|
properties?: {
|
|
14
25
|
firstname?: string;
|
|
15
26
|
lastname?: string;
|
|
@@ -22,38 +33,114 @@ interface HubspotContactData {
|
|
|
22
33
|
|
|
23
34
|
interface HubspotCompanyData {
|
|
24
35
|
id?: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
domain?: string;
|
|
38
|
+
industry?: string;
|
|
39
|
+
employeeCount?: number | null;
|
|
40
|
+
createdAt?: string;
|
|
41
|
+
updatedAt?: string;
|
|
42
|
+
/** Legacy shape: raw HubSpot properties */
|
|
25
43
|
properties?: {
|
|
26
44
|
name?: string;
|
|
27
45
|
domain?: string;
|
|
28
46
|
industry?: string;
|
|
29
47
|
phone?: string;
|
|
48
|
+
numberofemployees?: string;
|
|
30
49
|
[key: string]: unknown;
|
|
31
50
|
};
|
|
32
51
|
}
|
|
33
52
|
|
|
53
|
+
interface HubspotDealData {
|
|
54
|
+
id?: string;
|
|
55
|
+
name?: string;
|
|
56
|
+
amount?: number | null;
|
|
57
|
+
stage?: string;
|
|
58
|
+
closeDate?: string;
|
|
59
|
+
pipeline?: string;
|
|
60
|
+
createdAt?: string;
|
|
61
|
+
updatedAt?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
34
64
|
interface HubSpotRendererProps {
|
|
35
65
|
data: unknown;
|
|
36
66
|
}
|
|
37
67
|
|
|
68
|
+
/* ------------------------------------------------------------------ */
|
|
69
|
+
/* Type guards */
|
|
70
|
+
/* ------------------------------------------------------------------ */
|
|
71
|
+
|
|
38
72
|
function isHubspotContact(data: unknown): data is HubspotContactData {
|
|
39
73
|
if (typeof data !== "object" || data === null) return false;
|
|
40
74
|
const d = data as any;
|
|
41
|
-
// Normalized shape from Supyagent API
|
|
42
75
|
if ("firstName" in d || "lastName" in d || (d.email && d.id)) return true;
|
|
43
|
-
// Raw HubSpot properties shape
|
|
44
76
|
const props = d.properties;
|
|
45
77
|
return props && ("firstname" in props || "lastname" in props || "email" in props);
|
|
46
78
|
}
|
|
47
79
|
|
|
48
80
|
function isHubspotCompany(data: unknown): data is HubspotCompanyData {
|
|
49
81
|
if (typeof data !== "object" || data === null) return false;
|
|
50
|
-
const
|
|
82
|
+
const d = data as any;
|
|
83
|
+
// Flat shape from Supyagent API (has domain at top level)
|
|
84
|
+
if ("domain" in d && !("email" in d) && !("amount" in d)) return true;
|
|
85
|
+
// Legacy raw HubSpot properties shape
|
|
86
|
+
const props = d.properties;
|
|
51
87
|
return props && ("name" in props || "domain" in props);
|
|
52
88
|
}
|
|
53
89
|
|
|
90
|
+
function isHubspotDeal(data: unknown): data is HubspotDealData {
|
|
91
|
+
if (typeof data !== "object" || data === null) return false;
|
|
92
|
+
const d = data as any;
|
|
93
|
+
return ("amount" in d || "stage" in d || "closeDate" in d) && !("email" in d);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ------------------------------------------------------------------ */
|
|
97
|
+
/* Helpers */
|
|
98
|
+
/* ------------------------------------------------------------------ */
|
|
99
|
+
|
|
100
|
+
function formatCurrency(value: number): string {
|
|
101
|
+
try {
|
|
102
|
+
return new Intl.NumberFormat(undefined, {
|
|
103
|
+
style: "currency",
|
|
104
|
+
currency: "USD",
|
|
105
|
+
minimumFractionDigits: 0,
|
|
106
|
+
maximumFractionDigits: 0,
|
|
107
|
+
}).format(value);
|
|
108
|
+
} catch {
|
|
109
|
+
return `$${value.toLocaleString()}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function formatDate(dateStr: string): string {
|
|
114
|
+
try {
|
|
115
|
+
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
116
|
+
month: "short",
|
|
117
|
+
day: "numeric",
|
|
118
|
+
year: "numeric",
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
return dateStr;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getStageBadge(stage?: string) {
|
|
126
|
+
if (!stage) return null;
|
|
127
|
+
const s = stage.toLowerCase();
|
|
128
|
+
if (s === "closedwon" || s === "closed won")
|
|
129
|
+
return { label: "Won", className: "text-green-500 bg-green-500/10" };
|
|
130
|
+
if (s === "closedlost" || s === "closed lost")
|
|
131
|
+
return { label: "Lost", className: "text-red-400 bg-red-400/10" };
|
|
132
|
+
// Custom stage IDs — show as "In Progress"
|
|
133
|
+
if (/^\d+$/.test(stage))
|
|
134
|
+
return { label: "In Progress", className: "text-blue-400 bg-blue-400/10" };
|
|
135
|
+
return { label: stage, className: "text-muted-foreground bg-muted" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ------------------------------------------------------------------ */
|
|
139
|
+
/* Cards */
|
|
140
|
+
/* ------------------------------------------------------------------ */
|
|
141
|
+
|
|
54
142
|
function ContactCard({ contact }: { contact: HubspotContactData }) {
|
|
55
143
|
const p = contact.properties || {};
|
|
56
|
-
// Support both normalized (firstName) and raw (properties.firstname) shapes
|
|
57
144
|
const first = contact.firstName || p.firstname;
|
|
58
145
|
const last = contact.lastName || p.lastname;
|
|
59
146
|
const email = contact.email || p.email;
|
|
@@ -92,107 +179,252 @@ function ContactCard({ contact }: { contact: HubspotContactData }) {
|
|
|
92
179
|
|
|
93
180
|
function CompanyCard({ company }: { company: HubspotCompanyData }) {
|
|
94
181
|
const p = company.properties || {};
|
|
182
|
+
// Support both flat shape (name at top level) and legacy (properties.name)
|
|
183
|
+
const name = company.name || p.name;
|
|
184
|
+
const domain = company.domain || p.domain;
|
|
185
|
+
const industry = company.industry || p.industry;
|
|
186
|
+
const phone = p.phone;
|
|
187
|
+
const employeeCount =
|
|
188
|
+
company.employeeCount ?? (p.numberofemployees ? Number(p.numberofemployees) : null);
|
|
95
189
|
|
|
96
190
|
return (
|
|
97
191
|
<div className="rounded-lg border border-border bg-card p-3 space-y-1.5">
|
|
98
192
|
<div className="flex items-start gap-2">
|
|
99
193
|
<Building2 className="h-4 w-4 text-muted-foreground mt-0.5 shrink-0" />
|
|
100
194
|
<div className="min-w-0 flex-1">
|
|
101
|
-
<p className="text-sm font-medium text-foreground">
|
|
102
|
-
|
|
103
|
-
|
|
195
|
+
<p className="text-sm font-medium text-foreground">
|
|
196
|
+
{name || "Unknown company"}
|
|
197
|
+
</p>
|
|
198
|
+
{industry && (
|
|
199
|
+
<p className="text-xs text-muted-foreground">{industry}</p>
|
|
104
200
|
)}
|
|
105
201
|
</div>
|
|
106
202
|
</div>
|
|
107
|
-
<div className="flex flex-wrap gap-x-4 gap-y-1 pl-6">
|
|
108
|
-
{
|
|
109
|
-
<span className="
|
|
203
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 pl-6 text-xs text-muted-foreground">
|
|
204
|
+
{domain && (
|
|
205
|
+
<span className="flex items-center gap-1">
|
|
206
|
+
<Globe className="h-3 w-3" />
|
|
207
|
+
{domain}
|
|
208
|
+
</span>
|
|
110
209
|
)}
|
|
111
|
-
{
|
|
112
|
-
<span className="flex items-center gap-1
|
|
210
|
+
{phone && (
|
|
211
|
+
<span className="flex items-center gap-1">
|
|
113
212
|
<Phone className="h-3 w-3" />
|
|
114
|
-
{
|
|
213
|
+
{phone}
|
|
214
|
+
</span>
|
|
215
|
+
)}
|
|
216
|
+
{employeeCount != null && employeeCount > 0 && (
|
|
217
|
+
<span className="flex items-center gap-1">
|
|
218
|
+
<Users className="h-3 w-3" />
|
|
219
|
+
{employeeCount.toLocaleString()} employees
|
|
220
|
+
</span>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function DealCard({ deal }: { deal: HubspotDealData }) {
|
|
228
|
+
const stageBadge = getStageBadge(deal.stage);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div className="rounded-lg border border-border bg-card p-3 space-y-1.5">
|
|
232
|
+
<div className="flex items-start gap-2">
|
|
233
|
+
<Briefcase className="h-4 w-4 text-muted-foreground mt-0.5 shrink-0" />
|
|
234
|
+
<div className="min-w-0 flex-1">
|
|
235
|
+
<p className="text-sm font-medium text-foreground">
|
|
236
|
+
{deal.name || "Untitled deal"}
|
|
237
|
+
</p>
|
|
238
|
+
<div className="flex items-center gap-2 mt-0.5 flex-wrap">
|
|
239
|
+
{deal.amount != null && (
|
|
240
|
+
<span className="flex items-center gap-1 text-xs font-medium text-foreground">
|
|
241
|
+
<DollarSign className="h-3 w-3" />
|
|
242
|
+
{formatCurrency(deal.amount)}
|
|
243
|
+
</span>
|
|
244
|
+
)}
|
|
245
|
+
{stageBadge && (
|
|
246
|
+
<span
|
|
247
|
+
className={`rounded-full px-2 py-0.5 text-xs ${stageBadge.className}`}
|
|
248
|
+
>
|
|
249
|
+
{stageBadge.label}
|
|
250
|
+
</span>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 pl-6 text-xs text-muted-foreground">
|
|
256
|
+
{deal.closeDate && (
|
|
257
|
+
<span className="flex items-center gap-1">
|
|
258
|
+
<Calendar className="h-3 w-3" />
|
|
259
|
+
Close: {formatDate(deal.closeDate)}
|
|
115
260
|
</span>
|
|
116
261
|
)}
|
|
262
|
+
{deal.pipeline && deal.pipeline !== "default" && (
|
|
263
|
+
<span>{deal.pipeline}</span>
|
|
264
|
+
)}
|
|
117
265
|
</div>
|
|
118
266
|
</div>
|
|
119
267
|
);
|
|
120
268
|
}
|
|
121
269
|
|
|
270
|
+
/* ------------------------------------------------------------------ */
|
|
271
|
+
/* List wrapper with optional paging indicator */
|
|
272
|
+
/* ------------------------------------------------------------------ */
|
|
273
|
+
|
|
274
|
+
function ListWrapper({
|
|
275
|
+
children,
|
|
276
|
+
hasMore,
|
|
277
|
+
}: {
|
|
278
|
+
children: React.ReactNode;
|
|
279
|
+
hasMore?: boolean;
|
|
280
|
+
}) {
|
|
281
|
+
return (
|
|
282
|
+
<div className="space-y-2">
|
|
283
|
+
{children}
|
|
284
|
+
{hasMore && (
|
|
285
|
+
<p className="text-xs text-muted-foreground text-center pt-1">
|
|
286
|
+
More results available
|
|
287
|
+
</p>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function hasPaging(data: unknown): boolean {
|
|
294
|
+
if (typeof data !== "object" || data === null) return false;
|
|
295
|
+
const paging = (data as any).paging;
|
|
296
|
+
return paging && typeof paging === "object" && paging.next;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* ------------------------------------------------------------------ */
|
|
300
|
+
/* Renderer */
|
|
301
|
+
/* ------------------------------------------------------------------ */
|
|
302
|
+
|
|
122
303
|
export function HubSpotRenderer({ data }: HubSpotRendererProps) {
|
|
123
304
|
// Single contact
|
|
124
305
|
if (isHubspotContact(data)) {
|
|
125
306
|
return <ContactCard contact={data} />;
|
|
126
307
|
}
|
|
127
308
|
|
|
309
|
+
// Single deal
|
|
310
|
+
if (isHubspotDeal(data)) {
|
|
311
|
+
return <DealCard deal={data} />;
|
|
312
|
+
}
|
|
313
|
+
|
|
128
314
|
// Single company
|
|
129
315
|
if (isHubspotCompany(data)) {
|
|
130
316
|
return <CompanyCard company={data} />;
|
|
131
317
|
}
|
|
132
318
|
|
|
133
|
-
//
|
|
319
|
+
// Flat array
|
|
134
320
|
if (Array.isArray(data)) {
|
|
321
|
+
const deals = data.filter(isHubspotDeal);
|
|
322
|
+
if (deals.length > 0) {
|
|
323
|
+
return (
|
|
324
|
+
<ListWrapper>
|
|
325
|
+
{deals.map((d, i) => (
|
|
326
|
+
<DealCard key={d.id || i} deal={d} />
|
|
327
|
+
))}
|
|
328
|
+
</ListWrapper>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
135
331
|
const contacts = data.filter(isHubspotContact);
|
|
136
332
|
if (contacts.length > 0) {
|
|
137
333
|
return (
|
|
138
|
-
<
|
|
334
|
+
<ListWrapper>
|
|
139
335
|
{contacts.map((c, i) => (
|
|
140
336
|
<ContactCard key={c.id || i} contact={c} />
|
|
141
337
|
))}
|
|
142
|
-
</
|
|
338
|
+
</ListWrapper>
|
|
143
339
|
);
|
|
144
340
|
}
|
|
145
341
|
const companies = data.filter(isHubspotCompany);
|
|
146
342
|
if (companies.length > 0) {
|
|
147
343
|
return (
|
|
148
|
-
<
|
|
344
|
+
<ListWrapper>
|
|
149
345
|
{companies.map((c, i) => (
|
|
150
346
|
<CompanyCard key={c.id || i} company={c} />
|
|
151
347
|
))}
|
|
152
|
-
</
|
|
348
|
+
</ListWrapper>
|
|
153
349
|
);
|
|
154
350
|
}
|
|
155
351
|
}
|
|
156
352
|
|
|
157
|
-
//
|
|
158
|
-
if (typeof data === "object" && data !== null
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
353
|
+
// Wrapper objects: { contacts: [...] }, { companies: [...] }, { deals: [...] }
|
|
354
|
+
if (typeof data === "object" && data !== null) {
|
|
355
|
+
const d = data as any;
|
|
356
|
+
const more = hasPaging(data);
|
|
357
|
+
|
|
358
|
+
if ("contacts" in d && Array.isArray(d.contacts)) {
|
|
359
|
+
const valid = d.contacts.filter(isHubspotContact);
|
|
162
360
|
if (valid.length > 0) {
|
|
163
361
|
return (
|
|
164
|
-
<
|
|
165
|
-
{valid.map((c, i) => (
|
|
362
|
+
<ListWrapper hasMore={more}>
|
|
363
|
+
{valid.map((c: HubspotContactData, i: number) => (
|
|
166
364
|
<ContactCard key={c.id || i} contact={c} />
|
|
167
365
|
))}
|
|
168
|
-
</
|
|
366
|
+
</ListWrapper>
|
|
169
367
|
);
|
|
170
368
|
}
|
|
171
369
|
}
|
|
172
|
-
}
|
|
173
370
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
371
|
+
if ("companies" in d && Array.isArray(d.companies)) {
|
|
372
|
+
const valid = d.companies.filter(isHubspotCompany);
|
|
373
|
+
if (valid.length > 0) {
|
|
374
|
+
return (
|
|
375
|
+
<ListWrapper hasMore={more}>
|
|
376
|
+
{valid.map((c: HubspotCompanyData, i: number) => (
|
|
377
|
+
<CompanyCard key={c.id || i} company={c} />
|
|
378
|
+
))}
|
|
379
|
+
</ListWrapper>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if ("deals" in d && Array.isArray(d.deals)) {
|
|
385
|
+
const valid = d.deals.filter(isHubspotDeal);
|
|
386
|
+
if (valid.length > 0) {
|
|
387
|
+
return (
|
|
388
|
+
<ListWrapper hasMore={more}>
|
|
389
|
+
{valid.map((deal: HubspotDealData, i: number) => (
|
|
390
|
+
<DealCard key={deal.id || i} deal={deal} />
|
|
391
|
+
))}
|
|
392
|
+
</ListWrapper>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Raw HubSpot shape: { results: [...] }
|
|
398
|
+
if ("results" in d && Array.isArray(d.results)) {
|
|
399
|
+
const results = d.results;
|
|
400
|
+
const deals = results.filter(isHubspotDeal);
|
|
401
|
+
if (deals.length > 0) {
|
|
402
|
+
return (
|
|
403
|
+
<ListWrapper hasMore={more}>
|
|
404
|
+
{deals.map((deal: HubspotDealData, i: number) => (
|
|
405
|
+
<DealCard key={deal.id || i} deal={deal} />
|
|
406
|
+
))}
|
|
407
|
+
</ListWrapper>
|
|
408
|
+
);
|
|
409
|
+
}
|
|
178
410
|
const contacts = results.filter(isHubspotContact);
|
|
179
411
|
if (contacts.length > 0) {
|
|
180
412
|
return (
|
|
181
|
-
<
|
|
182
|
-
{contacts.map((c, i) => (
|
|
413
|
+
<ListWrapper hasMore={more}>
|
|
414
|
+
{contacts.map((c: HubspotContactData, i: number) => (
|
|
183
415
|
<ContactCard key={c.id || i} contact={c} />
|
|
184
416
|
))}
|
|
185
|
-
</
|
|
417
|
+
</ListWrapper>
|
|
186
418
|
);
|
|
187
419
|
}
|
|
188
420
|
const companies = results.filter(isHubspotCompany);
|
|
189
421
|
if (companies.length > 0) {
|
|
190
422
|
return (
|
|
191
|
-
<
|
|
192
|
-
{companies.map((c, i) => (
|
|
423
|
+
<ListWrapper hasMore={more}>
|
|
424
|
+
{companies.map((c: HubspotCompanyData, i: number) => (
|
|
193
425
|
<CompanyCard key={c.id || i} company={c} />
|
|
194
426
|
))}
|
|
195
|
-
</
|
|
427
|
+
</ListWrapper>
|
|
196
428
|
);
|
|
197
429
|
}
|
|
198
430
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"{{aiProviderPackage}}": "{{aiProviderVersion}}",
|
|
18
18
|
"@prisma/client": "^6.2.0",
|
|
19
19
|
"@radix-ui/react-collapsible": "^1.1.0",
|
|
20
|
-
"@supyagent/sdk": "^0.1.
|
|
20
|
+
"@supyagent/sdk": "^0.1.14",
|
|
21
21
|
"class-variance-authority": "^0.7.0",
|
|
22
22
|
"ai": "^6.0.0",
|
|
23
23
|
"clsx": "^2.1.0",
|