@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.
@@ -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 &gt; Aparelhos conectados &gt; 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";