@greatapps/greatchat-ui 0.1.4 → 0.1.6
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 +122 -1
- package/dist/index.js +1692 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/components/channel-card.tsx +100 -0
- package/src/components/channel-create-dialog.tsx +126 -0
- package/src/components/channel-edit-dialog.tsx +132 -0
- package/src/components/channels-page.tsx +242 -0
- package/src/components/chat-dashboard.tsx +433 -0
- package/src/components/contact-form-dialog.tsx +139 -0
- package/src/components/contacts-page.tsx +41 -0
- package/src/components/contacts-table.tsx +218 -0
- package/src/components/data-table.tsx +185 -0
- package/src/components/inbox-page.tsx +174 -0
- package/src/components/index.ts +6 -0
- package/src/components/ui/card.tsx +94 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/table.tsx +101 -0
- package/src/components/whatsapp-icon.tsx +21 -0
- package/src/components/whatsapp-qr-dialog.tsx +147 -0
- package/src/components/whatsapp-status-badge.tsx +72 -0
- package/src/index.ts +40 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Label as LabelPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Label({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<LabelPrimitive.Root
|
|
14
|
+
data-slot="label"
|
|
15
|
+
className={cn(
|
|
16
|
+
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Label }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Progress as ProgressPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Progress({
|
|
9
|
+
className,
|
|
10
|
+
value,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
|
13
|
+
return (
|
|
14
|
+
<ProgressPrimitive.Root
|
|
15
|
+
data-slot="progress"
|
|
16
|
+
className={cn(
|
|
17
|
+
"bg-muted h-1.5 rounded-full relative flex w-full items-center overflow-x-hidden",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<ProgressPrimitive.Indicator
|
|
23
|
+
data-slot="progress-indicator"
|
|
24
|
+
className="bg-primary size-full flex-1 transition-all"
|
|
25
|
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
26
|
+
/>
|
|
27
|
+
</ProgressPrimitive.Root>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Progress }
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
|
10
|
+
<table
|
|
11
|
+
data-slot="table"
|
|
12
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
20
|
+
return (
|
|
21
|
+
<thead
|
|
22
|
+
data-slot="table-header"
|
|
23
|
+
className={cn("[&_tr]:border-b", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
30
|
+
return (
|
|
31
|
+
<tbody
|
|
32
|
+
data-slot="table-body"
|
|
33
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
40
|
+
return (
|
|
41
|
+
<tfoot
|
|
42
|
+
data-slot="table-footer"
|
|
43
|
+
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
50
|
+
return (
|
|
51
|
+
<tr
|
|
52
|
+
data-slot="table-row"
|
|
53
|
+
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
60
|
+
return (
|
|
61
|
+
<th
|
|
62
|
+
data-slot="table-head"
|
|
63
|
+
className={cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
70
|
+
return (
|
|
71
|
+
<td
|
|
72
|
+
data-slot="table-cell"
|
|
73
|
+
className={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function TableCaption({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: React.ComponentProps<"caption">) {
|
|
83
|
+
return (
|
|
84
|
+
<caption
|
|
85
|
+
data-slot="table-caption"
|
|
86
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
Table,
|
|
94
|
+
TableHeader,
|
|
95
|
+
TableBody,
|
|
96
|
+
TableFooter,
|
|
97
|
+
TableHead,
|
|
98
|
+
TableRow,
|
|
99
|
+
TableCell,
|
|
100
|
+
TableCaption,
|
|
101
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
export function WhatsappIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
width="24"
|
|
8
|
+
height="24"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
strokeWidth={2}
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9" />
|
|
18
|
+
<path d="M9 10a.5 .5 0 0 0 1 0v-1a.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a.5 .5 0 0 0 0 -1h-1a.5 .5 0 0 0 0 1" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import type { GchatHookConfig } from "../hooks/types";
|
|
5
|
+
import { useConnectChannel, useChannelQR, useChannelWhatsappStatus } from "../hooks/use-channels";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
} from "./ui/dialog";
|
|
13
|
+
import { Button } from "./ui/button";
|
|
14
|
+
import { Loader2, Check, AlertCircle, RefreshCw } from "lucide-react";
|
|
15
|
+
import { toast } from "sonner";
|
|
16
|
+
|
|
17
|
+
type QRState = "connecting" | "waiting_qr" | "showing_qr" | "connected" | "error";
|
|
18
|
+
|
|
19
|
+
export interface WhatsappQrDialogProps {
|
|
20
|
+
open: boolean;
|
|
21
|
+
onOpenChange: (open: boolean) => void;
|
|
22
|
+
channelId: number;
|
|
23
|
+
config: GchatHookConfig;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function WhatsappQrDialog({
|
|
27
|
+
open,
|
|
28
|
+
onOpenChange,
|
|
29
|
+
channelId,
|
|
30
|
+
config,
|
|
31
|
+
}: WhatsappQrDialogProps) {
|
|
32
|
+
const connectChannel = useConnectChannel(config);
|
|
33
|
+
const [state, setState] = useState<QRState>("connecting");
|
|
34
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
35
|
+
|
|
36
|
+
const { data: qrUrl } = useChannelQR(
|
|
37
|
+
config,
|
|
38
|
+
channelId,
|
|
39
|
+
open && (state === "waiting_qr" || state === "showing_qr"),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const { data: status } = useChannelWhatsappStatus(
|
|
43
|
+
config,
|
|
44
|
+
channelId,
|
|
45
|
+
open && state !== "connected",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const startConnect = useCallback(() => {
|
|
49
|
+
setState("connecting");
|
|
50
|
+
setErrorMsg("");
|
|
51
|
+
connectChannel.mutate(channelId, {
|
|
52
|
+
onSuccess: () => setState("waiting_qr"),
|
|
53
|
+
onError: (err) => {
|
|
54
|
+
setState("error");
|
|
55
|
+
setErrorMsg(err.message || "Erro ao conectar");
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}, [channelId, connectChannel]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!open) {
|
|
62
|
+
setState("connecting");
|
|
63
|
+
setErrorMsg("");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
startConnect();
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, [open, channelId]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (qrUrl && (state === "waiting_qr" || state === "showing_qr")) {
|
|
72
|
+
setState("showing_qr");
|
|
73
|
+
}
|
|
74
|
+
}, [qrUrl, state]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (status?.connected && status?.logged_in) {
|
|
78
|
+
setState("connected");
|
|
79
|
+
toast.success("WhatsApp conectado com sucesso!");
|
|
80
|
+
setTimeout(() => onOpenChange(false), 1500);
|
|
81
|
+
}
|
|
82
|
+
}, [status, onOpenChange]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
86
|
+
<DialogContent className="sm:max-w-md">
|
|
87
|
+
<DialogHeader>
|
|
88
|
+
<DialogTitle>Conectar WhatsApp</DialogTitle>
|
|
89
|
+
<DialogDescription>
|
|
90
|
+
Escaneie o QR code com seu WhatsApp para conectar
|
|
91
|
+
</DialogDescription>
|
|
92
|
+
</DialogHeader>
|
|
93
|
+
|
|
94
|
+
<div className="flex min-h-[300px] items-center justify-center">
|
|
95
|
+
{state === "connecting" && (
|
|
96
|
+
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
|
97
|
+
<Loader2 className="h-8 w-8 animate-spin" />
|
|
98
|
+
<p className="text-sm">Iniciando conexão...</p>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{state === "waiting_qr" && (
|
|
103
|
+
<div className="flex flex-col items-center gap-3 text-muted-foreground">
|
|
104
|
+
<Loader2 className="h-8 w-8 animate-spin" />
|
|
105
|
+
<p className="text-sm">Gerando QR code...</p>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{state === "showing_qr" && qrUrl && (
|
|
110
|
+
<div className="flex flex-col items-center gap-3">
|
|
111
|
+
<img
|
|
112
|
+
src={qrUrl}
|
|
113
|
+
alt="QR Code WhatsApp"
|
|
114
|
+
className="h-64 w-64 rounded-lg border"
|
|
115
|
+
/>
|
|
116
|
+
<p className="text-sm text-muted-foreground">
|
|
117
|
+
Abra o WhatsApp > Aparelhos conectados > Conectar
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{state === "connected" && (
|
|
123
|
+
<div className="flex flex-col items-center gap-3 text-green-600">
|
|
124
|
+
<Check className="h-12 w-12" />
|
|
125
|
+
<p className="text-lg font-medium">Conectado!</p>
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{state === "error" && (
|
|
130
|
+
<div className="flex flex-col items-center gap-3 text-destructive">
|
|
131
|
+
<AlertCircle className="h-8 w-8" />
|
|
132
|
+
<p className="text-sm">{errorMsg || "Erro ao gerar QR code"}</p>
|
|
133
|
+
<Button
|
|
134
|
+
variant="outline"
|
|
135
|
+
size="sm"
|
|
136
|
+
onClick={startConnect}
|
|
137
|
+
>
|
|
138
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
139
|
+
Tentar novamente
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</DialogContent>
|
|
145
|
+
</Dialog>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { WhatsappStatus } from "../types";
|
|
4
|
+
import { Badge } from "./ui/badge";
|
|
5
|
+
|
|
6
|
+
export interface WhatsappStatusBadgeProps {
|
|
7
|
+
status: WhatsappStatus | undefined | null;
|
|
8
|
+
hasSession: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function WhatsappStatusBadge({
|
|
12
|
+
status,
|
|
13
|
+
hasSession,
|
|
14
|
+
}: WhatsappStatusBadgeProps) {
|
|
15
|
+
if (!hasSession) {
|
|
16
|
+
return (
|
|
17
|
+
<Badge variant="outline" className="text-xs text-zinc-500 border-zinc-300">
|
|
18
|
+
Sem sessão
|
|
19
|
+
</Badge>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!status) {
|
|
24
|
+
return (
|
|
25
|
+
<Badge variant="outline" className="text-xs text-zinc-500 border-zinc-300">
|
|
26
|
+
Verificando...
|
|
27
|
+
</Badge>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (status.connected && status.logged_in) {
|
|
32
|
+
return (
|
|
33
|
+
<Badge
|
|
34
|
+
variant="outline"
|
|
35
|
+
className="text-xs bg-green-500/10 text-green-600 border-green-200"
|
|
36
|
+
>
|
|
37
|
+
Conectado
|
|
38
|
+
</Badge>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (status.connected && !status.logged_in) {
|
|
43
|
+
return (
|
|
44
|
+
<Badge
|
|
45
|
+
variant="outline"
|
|
46
|
+
className="text-xs bg-yellow-500/10 text-yellow-600 border-yellow-200"
|
|
47
|
+
>
|
|
48
|
+
Reconectando...
|
|
49
|
+
</Badge>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (status.logged_in && !status.connected) {
|
|
54
|
+
return (
|
|
55
|
+
<Badge
|
|
56
|
+
variant="outline"
|
|
57
|
+
className="text-xs bg-yellow-500/10 text-yellow-600 border-yellow-200"
|
|
58
|
+
>
|
|
59
|
+
Desconectado
|
|
60
|
+
</Badge>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Badge
|
|
66
|
+
variant="outline"
|
|
67
|
+
className="text-xs bg-red-500/10 text-red-600 border-red-200"
|
|
68
|
+
>
|
|
69
|
+
Offline
|
|
70
|
+
</Badge>
|
|
71
|
+
);
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { groupMessagesByDate, formatDateGroup, formatMessageTime } from "./utils
|
|
|
21
21
|
// Hooks
|
|
22
22
|
export * from "./hooks";
|
|
23
23
|
|
|
24
|
-
// Components
|
|
24
|
+
// Base Components
|
|
25
25
|
export {
|
|
26
26
|
ChatView,
|
|
27
27
|
ChatInput,
|
|
@@ -31,6 +31,8 @@ export {
|
|
|
31
31
|
InboxSidebar,
|
|
32
32
|
ContactInfoPanel,
|
|
33
33
|
NewConversationDialog,
|
|
34
|
+
ChannelCard,
|
|
35
|
+
WhatsappStatusBadge,
|
|
34
36
|
} from "./components";
|
|
35
37
|
export type {
|
|
36
38
|
ChatViewProps,
|
|
@@ -41,4 +43,41 @@ export type {
|
|
|
41
43
|
InboxSidebarProps,
|
|
42
44
|
ContactInfoPanelProps,
|
|
43
45
|
NewConversationDialogProps,
|
|
46
|
+
ChannelCardProps,
|
|
47
|
+
WhatsappStatusBadgeProps,
|
|
44
48
|
} from "./components";
|
|
49
|
+
|
|
50
|
+
// Icons
|
|
51
|
+
export { WhatsappIcon } from "./components/whatsapp-icon";
|
|
52
|
+
|
|
53
|
+
// Dialogs & Forms
|
|
54
|
+
export { WhatsappQrDialog } from "./components/whatsapp-qr-dialog";
|
|
55
|
+
export type { WhatsappQrDialogProps } from "./components/whatsapp-qr-dialog";
|
|
56
|
+
|
|
57
|
+
export { ChannelCreateDialog } from "./components/channel-create-dialog";
|
|
58
|
+
export type { ChannelCreateDialogProps } from "./components/channel-create-dialog";
|
|
59
|
+
|
|
60
|
+
export { ChannelEditDialog } from "./components/channel-edit-dialog";
|
|
61
|
+
export type { ChannelEditDialogProps } from "./components/channel-edit-dialog";
|
|
62
|
+
|
|
63
|
+
export { ContactFormDialog } from "./components/contact-form-dialog";
|
|
64
|
+
export type { ContactFormDialogProps } from "./components/contact-form-dialog";
|
|
65
|
+
|
|
66
|
+
export { ContactsTable } from "./components/contacts-table";
|
|
67
|
+
export type { ContactsTableProps } from "./components/contacts-table";
|
|
68
|
+
|
|
69
|
+
export { DataTable } from "./components/data-table";
|
|
70
|
+
export type { DataTableProps } from "./components/data-table";
|
|
71
|
+
|
|
72
|
+
// Page Compositions
|
|
73
|
+
export { InboxPage } from "./components/inbox-page";
|
|
74
|
+
export type { InboxPageProps } from "./components/inbox-page";
|
|
75
|
+
|
|
76
|
+
export { ChannelsPage } from "./components/channels-page";
|
|
77
|
+
export type { ChannelsPageProps } from "./components/channels-page";
|
|
78
|
+
|
|
79
|
+
export { ContactsPage } from "./components/contacts-page";
|
|
80
|
+
export type { ContactsPageProps } from "./components/contacts-page";
|
|
81
|
+
|
|
82
|
+
export { ChatDashboard } from "./components/chat-dashboard";
|
|
83
|
+
export type { ChatDashboardProps } from "./components/chat-dashboard";
|