@greatapps/greatchat-ui 0.1.5 → 0.2.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.
Files changed (42) hide show
  1. package/dist/index.d.ts +106 -5
  2. package/dist/index.js +1787 -1116
  3. package/dist/index.js.map +1 -1
  4. package/package.json +17 -13
  5. package/src/components/channel-card.tsx +1 -1
  6. package/src/components/channel-create-dialog.tsx +126 -0
  7. package/src/components/channel-edit-dialog.tsx +132 -0
  8. package/src/components/channels-page.tsx +242 -0
  9. package/src/components/chat-dashboard.tsx +433 -0
  10. package/src/components/chat-input.tsx +1 -2
  11. package/src/components/chat-view.tsx +1 -8
  12. package/src/components/contact-avatar.tsx +1 -2
  13. package/src/components/contact-form-dialog.tsx +139 -0
  14. package/src/components/contact-info-panel.tsx +1 -4
  15. package/src/components/contacts-page.tsx +41 -0
  16. package/src/components/contacts-table.tsx +216 -0
  17. package/src/components/data-table.tsx +185 -0
  18. package/src/components/inbox-item.tsx +6 -4
  19. package/src/components/inbox-page.tsx +167 -0
  20. package/src/components/inbox-sidebar.tsx +1 -5
  21. package/src/components/message-bubble.tsx +4 -6
  22. package/src/components/new-conversation-dialog.tsx +2 -6
  23. package/src/components/whatsapp-icon.tsx +21 -0
  24. package/src/components/whatsapp-qr-dialog.tsx +147 -0
  25. package/src/components/whatsapp-status-badge.tsx +1 -1
  26. package/src/index.ts +37 -2
  27. package/src/components/ui/alert-dialog.tsx +0 -167
  28. package/src/components/ui/avatar.tsx +0 -51
  29. package/src/components/ui/badge.tsx +0 -44
  30. package/src/components/ui/button.tsx +0 -62
  31. package/src/components/ui/command.tsx +0 -106
  32. package/src/components/ui/dialog.tsx +0 -133
  33. package/src/components/ui/dropdown-menu.tsx +0 -173
  34. package/src/components/ui/input.tsx +0 -19
  35. package/src/components/ui/scroll-area.tsx +0 -50
  36. package/src/components/ui/select.tsx +0 -156
  37. package/src/components/ui/separator.tsx +0 -26
  38. package/src/components/ui/skeleton.tsx +0 -16
  39. package/src/components/ui/tabs.tsx +0 -64
  40. package/src/components/ui/textarea.tsx +0 -18
  41. package/src/components/ui/tooltip.tsx +0 -58
  42. package/src/lib/utils.ts +0 -6
@@ -0,0 +1,216 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState } from "react";
4
+ import type { ColumnDef } from "@tanstack/react-table";
5
+ import type { Contact } from "../types";
6
+ import type { GchatHookConfig } from "../hooks/types";
7
+ import { useContacts, useDeleteContact } from "../hooks/use-contacts";
8
+ import { ContactFormDialog } from "./contact-form-dialog";
9
+ import { DataTable } from "./data-table";
10
+ import {
11
+ Input,
12
+ Tooltip,
13
+ TooltipTrigger,
14
+ TooltipContent,
15
+ AlertDialog,
16
+ AlertDialogAction,
17
+ AlertDialogCancel,
18
+ AlertDialogContent,
19
+ AlertDialogDescription,
20
+ AlertDialogFooter,
21
+ AlertDialogHeader,
22
+ AlertDialogTitle,
23
+ Button,
24
+ } from "@greatapps/greatauth-ui/ui";
25
+ import { Pencil, Trash2, Search } from "lucide-react";
26
+ import { format } from "date-fns";
27
+ import { ptBR } from "date-fns/locale";
28
+ import { toast } from "sonner";
29
+
30
+ function useColumns(
31
+ onEdit: (contact: Contact) => void,
32
+ onDelete: (id: number) => void,
33
+ ): ColumnDef<Contact>[] {
34
+ return [
35
+ {
36
+ accessorKey: "name",
37
+ header: "Nome",
38
+ cell: ({ row }) => (
39
+ <span className="font-medium">{row.original.name}</span>
40
+ ),
41
+ sortingFn: (rowA, rowB) =>
42
+ rowA.original.name.toLowerCase().localeCompare(rowB.original.name.toLowerCase()),
43
+ },
44
+ {
45
+ accessorKey: "phone_number",
46
+ header: "Telefone",
47
+ cell: ({ row }) => row.original.phone_number || "—",
48
+ sortingFn: (rowA, rowB) => {
49
+ const a = rowA.original.phone_number || "";
50
+ const b = rowB.original.phone_number || "";
51
+ return a.localeCompare(b);
52
+ },
53
+ },
54
+ {
55
+ accessorKey: "identifier",
56
+ header: "Identificador",
57
+ cell: ({ row }) => (
58
+ <span className="text-muted-foreground text-xs font-mono">
59
+ {row.original.identifier || "—"}
60
+ </span>
61
+ ),
62
+ sortingFn: (rowA, rowB) => {
63
+ const a = rowA.original.identifier || "";
64
+ const b = rowB.original.identifier || "";
65
+ return a.localeCompare(b);
66
+ },
67
+ },
68
+ {
69
+ accessorKey: "datetime_add",
70
+ header: "Criado em",
71
+ cell: ({ row }) => (
72
+ <span className="text-muted-foreground text-sm">
73
+ {format(new Date(row.original.datetime_add), "dd/MM/yyyy", { locale: ptBR })}
74
+ </span>
75
+ ),
76
+ },
77
+ {
78
+ id: "actions",
79
+ size: 80,
80
+ enableSorting: false,
81
+ cell: ({ row }) => (
82
+ <div className="flex items-center gap-1">
83
+ <Tooltip>
84
+ <TooltipTrigger asChild>
85
+ <Button
86
+ variant="ghost"
87
+ size="icon"
88
+ className="h-8 w-8"
89
+ onClick={() => onEdit(row.original)}
90
+ >
91
+ <Pencil className="h-4 w-4" />
92
+ </Button>
93
+ </TooltipTrigger>
94
+ <TooltipContent>Editar</TooltipContent>
95
+ </Tooltip>
96
+ <Tooltip>
97
+ <TooltipTrigger asChild>
98
+ <Button
99
+ variant="ghost"
100
+ size="icon"
101
+ className="h-8 w-8 text-destructive hover:text-destructive"
102
+ onClick={() => onDelete(row.original.id)}
103
+ >
104
+ <Trash2 className="h-4 w-4" />
105
+ </Button>
106
+ </TooltipTrigger>
107
+ <TooltipContent>Excluir</TooltipContent>
108
+ </Tooltip>
109
+ </div>
110
+ ),
111
+ },
112
+ ];
113
+ }
114
+
115
+ export interface ContactsTableProps {
116
+ config: GchatHookConfig;
117
+ }
118
+
119
+ export function ContactsTable({ config }: ContactsTableProps) {
120
+ const [search, setSearch] = useState("");
121
+ const [page, setPage] = useState(1);
122
+
123
+ const queryParams = useMemo(() => {
124
+ const params: Record<string, string> = {
125
+ limit: "15",
126
+ page: String(page),
127
+ };
128
+ if (search) {
129
+ params.search = search;
130
+ }
131
+ return params;
132
+ }, [search, page]);
133
+
134
+ const { data, isLoading } = useContacts(config, queryParams);
135
+ const deleteContact = useDeleteContact(config);
136
+ const [editContact, setEditContact] = useState<Contact | null>(null);
137
+ const [deleteId, setDeleteId] = useState<number | null>(null);
138
+
139
+ const contacts = data?.data || [];
140
+ const total = data?.total || 0;
141
+
142
+ const columns = useColumns(
143
+ (contact) => setEditContact(contact),
144
+ (id) => setDeleteId(id),
145
+ );
146
+
147
+ function handleDelete() {
148
+ if (!deleteId) return;
149
+ deleteContact.mutate(deleteId, {
150
+ onSuccess: () => {
151
+ toast.success("Contato excluído");
152
+ setDeleteId(null);
153
+ },
154
+ onError: () => toast.error("Erro ao excluir contato"),
155
+ });
156
+ }
157
+
158
+ function handleSearchChange(value: string) {
159
+ setSearch(value);
160
+ setPage(1);
161
+ }
162
+
163
+ return (
164
+ <>
165
+ <div className="flex items-center gap-3">
166
+ <div className="relative flex-1 max-w-md">
167
+ <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
168
+ <Input
169
+ placeholder="Buscar por nome, telefone ou identificador..."
170
+ value={search}
171
+ onChange={(e) => handleSearchChange(e.target.value)}
172
+ className="pl-9"
173
+ />
174
+ </div>
175
+ </div>
176
+
177
+ <DataTable
178
+ columns={columns}
179
+ data={contacts}
180
+ isLoading={isLoading}
181
+ emptyMessage="Nenhum contato encontrado"
182
+ total={total}
183
+ page={page}
184
+ onPageChange={setPage}
185
+ pageSize={15}
186
+ />
187
+
188
+ <ContactFormDialog
189
+ open={!!editContact}
190
+ onOpenChange={(open) => !open && setEditContact(null)}
191
+ contact={editContact ?? undefined}
192
+ config={config}
193
+ />
194
+
195
+ <AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
196
+ <AlertDialogContent>
197
+ <AlertDialogHeader>
198
+ <AlertDialogTitle>Excluir contato?</AlertDialogTitle>
199
+ <AlertDialogDescription>
200
+ Esta ação não pode ser desfeita. O contato será removido permanentemente.
201
+ </AlertDialogDescription>
202
+ </AlertDialogHeader>
203
+ <AlertDialogFooter>
204
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
205
+ <AlertDialogAction
206
+ onClick={handleDelete}
207
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
208
+ >
209
+ Excluir
210
+ </AlertDialogAction>
211
+ </AlertDialogFooter>
212
+ </AlertDialogContent>
213
+ </AlertDialog>
214
+ </>
215
+ );
216
+ }
@@ -0,0 +1,185 @@
1
+ "use client";
2
+
3
+ import {
4
+ type ColumnDef,
5
+ type SortingState,
6
+ flexRender,
7
+ getCoreRowModel,
8
+ getSortedRowModel,
9
+ useReactTable,
10
+ } from "@tanstack/react-table";
11
+ import { useState } from "react";
12
+ import {
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow,
19
+ Button,
20
+ Skeleton,
21
+ cn,
22
+ } from "@greatapps/greatauth-ui/ui";
23
+ import { ChevronLeft, ChevronRight, ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";
24
+
25
+ export interface DataTableProps<TData, TValue> {
26
+ columns: ColumnDef<TData, TValue>[];
27
+ data: TData[];
28
+ isLoading?: boolean;
29
+ emptyMessage?: string;
30
+ total?: number;
31
+ page?: number;
32
+ onPageChange?: (page: number) => void;
33
+ pageSize?: number;
34
+ onRowClick?: (row: TData) => void;
35
+ selectedRowId?: string | number | null;
36
+ getRowId?: (row: TData) => string | number;
37
+ compact?: boolean;
38
+ }
39
+
40
+ export function DataTable<TData, TValue>({
41
+ columns,
42
+ data,
43
+ isLoading,
44
+ emptyMessage = "Nenhum registro encontrado",
45
+ total,
46
+ page = 1,
47
+ onPageChange,
48
+ pageSize = 15,
49
+ onRowClick,
50
+ selectedRowId,
51
+ getRowId,
52
+ compact,
53
+ }: DataTableProps<TData, TValue>) {
54
+ const [sorting, setSorting] = useState<SortingState>([]);
55
+
56
+ const table = useReactTable({
57
+ data,
58
+ columns,
59
+ getCoreRowModel: getCoreRowModel(),
60
+ getSortedRowModel: getSortedRowModel(),
61
+ onSortingChange: setSorting,
62
+ state: { sorting },
63
+ });
64
+
65
+ const totalPages = total ? Math.ceil(total / pageSize) : 1;
66
+ const showPagination = onPageChange && total && total > pageSize;
67
+
68
+ return (
69
+ <div className="space-y-4">
70
+ <div className="rounded-md border">
71
+ <Table>
72
+ <TableHeader>
73
+ {table.getHeaderGroups().map((headerGroup) => (
74
+ <TableRow key={headerGroup.id}>
75
+ {headerGroup.headers.map((header) => (
76
+ <TableHead
77
+ key={header.id}
78
+ className={cn(compact && "py-1.5 text-xs")}
79
+ style={{ width: header.getSize() !== 150 ? header.getSize() : undefined }}
80
+ >
81
+ {header.isPlaceholder ? null : header.column.getCanSort() ? (
82
+ <button
83
+ type="button"
84
+ className="flex items-center gap-1 hover:text-foreground -ml-1 px-1 py-0.5 rounded cursor-pointer select-none"
85
+ onClick={header.column.getToggleSortingHandler()}
86
+ >
87
+ {flexRender(header.column.columnDef.header, header.getContext())}
88
+ {{
89
+ asc: <ArrowUp className="h-3.5 w-3.5" />,
90
+ desc: <ArrowDown className="h-3.5 w-3.5" />,
91
+ }[header.column.getIsSorted() as string] ?? (
92
+ <ArrowUpDown className="h-3.5 w-3.5 text-muted-foreground/50" />
93
+ )}
94
+ </button>
95
+ ) : (
96
+ flexRender(header.column.columnDef.header, header.getContext())
97
+ )}
98
+ </TableHead>
99
+ ))}
100
+ </TableRow>
101
+ ))}
102
+ </TableHeader>
103
+ <TableBody>
104
+ {isLoading ? (
105
+ Array.from({ length: compact ? 3 : 5 }).map((_, i) => (
106
+ <TableRow key={i}>
107
+ {columns.map((_, j) => (
108
+ <TableCell key={j} className={cn(compact && "py-1.5")}>
109
+ <Skeleton className="h-4 w-full max-w-[120px]" />
110
+ </TableCell>
111
+ ))}
112
+ </TableRow>
113
+ ))
114
+ ) : table.getRowModel().rows.length === 0 ? (
115
+ <TableRow>
116
+ <TableCell
117
+ colSpan={columns.length}
118
+ className={cn(
119
+ "text-center text-muted-foreground",
120
+ compact ? "py-4" : "py-8",
121
+ )}
122
+ >
123
+ {emptyMessage}
124
+ </TableCell>
125
+ </TableRow>
126
+ ) : (
127
+ table.getRowModel().rows.map((row) => {
128
+ const rowId = getRowId ? getRowId(row.original) : undefined;
129
+ const isSelected = selectedRowId != null && rowId != null && rowId === selectedRowId;
130
+
131
+ return (
132
+ <TableRow
133
+ key={row.id}
134
+ className={cn(
135
+ onRowClick && "cursor-pointer",
136
+ isSelected && "bg-accent",
137
+ )}
138
+ onClick={() => onRowClick?.(row.original)}
139
+ >
140
+ {row.getVisibleCells().map((cell) => (
141
+ <TableCell key={cell.id} className={cn(compact && "py-1.5")}>
142
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
143
+ </TableCell>
144
+ ))}
145
+ </TableRow>
146
+ );
147
+ })
148
+ )}
149
+ </TableBody>
150
+ </Table>
151
+ </div>
152
+
153
+ {showPagination && (
154
+ <div className="flex items-center justify-between px-2">
155
+ <p className="text-sm text-muted-foreground">
156
+ {total} registro{total !== 1 ? "s" : ""}
157
+ </p>
158
+ <div className="flex items-center gap-2">
159
+ <Button
160
+ variant="outline"
161
+ size="sm"
162
+ onClick={() => onPageChange(page - 1)}
163
+ disabled={page <= 1}
164
+ >
165
+ <ChevronLeft className="h-4 w-4" />
166
+ Anterior
167
+ </Button>
168
+ <span className="text-sm text-muted-foreground">
169
+ {page} de {totalPages}
170
+ </span>
171
+ <Button
172
+ variant="outline"
173
+ size="sm"
174
+ onClick={() => onPageChange(page + 1)}
175
+ disabled={page >= totalPages}
176
+ >
177
+ Próximo
178
+ <ChevronRight className="h-4 w-4" />
179
+ </Button>
180
+ </div>
181
+ </div>
182
+ )}
183
+ </div>
184
+ );
185
+ }
@@ -1,9 +1,11 @@
1
1
  import { useState } from "react";
2
2
  import type { Inbox } from "../types";
3
3
  import { ContactAvatar } from "./contact-avatar";
4
- import { Badge } from "./ui/badge";
5
- import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
6
4
  import {
5
+ Badge,
6
+ Tooltip,
7
+ TooltipTrigger,
8
+ TooltipContent,
7
9
  AlertDialog,
8
10
  AlertDialogAction,
9
11
  AlertDialogCancel,
@@ -12,11 +14,11 @@ import {
12
14
  AlertDialogFooter,
13
15
  AlertDialogHeader,
14
16
  AlertDialogTitle,
15
- } from "./ui/alert-dialog";
17
+ cn,
18
+ } from "@greatapps/greatauth-ui/ui";
16
19
  import { formatDistanceToNow } from "date-fns";
17
20
  import { ptBR } from "date-fns/locale";
18
21
  import { Trash2 } from "lucide-react";
19
- import { cn } from "../lib/utils";
20
22
 
21
23
  const statusColors: Record<string, string> = {
22
24
  open: "bg-green-500/10 text-green-600 border-green-200",
@@ -0,0 +1,167 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import type { Inbox, InboxMessage } from "../types";
5
+ import type { GchatHookConfig } from "../hooks/types";
6
+ import {
7
+ useInboxes,
8
+ useInboxStats,
9
+ useUpdateInbox,
10
+ useDeleteInbox,
11
+ } from "../hooks/use-inboxes";
12
+ import {
13
+ useInboxMessages,
14
+ useSendMessage,
15
+ useRetryMessage,
16
+ useRevokeMessage,
17
+ useEditMessage,
18
+ } from "../hooks/use-inbox-messages";
19
+ import { useGetContact } from "../hooks/use-contacts";
20
+ import { InboxSidebar } from "./inbox-sidebar";
21
+ import { ChatView } from "./chat-view";
22
+ import { ContactInfoPanel } from "./contact-info-panel";
23
+ import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@greatapps/greatauth-ui/ui";
24
+ import { MessageCircle, User } from "lucide-react";
25
+
26
+ export interface InboxPageProps {
27
+ config: GchatHookConfig;
28
+ filterChannelId?: number | null;
29
+ }
30
+
31
+ export function InboxPage({ config, filterChannelId }: InboxPageProps) {
32
+ const [selectedInbox, setSelectedInbox] = useState<Inbox | null>(null);
33
+ const [showContactPanel, setShowContactPanel] = useState(false);
34
+
35
+ const { data: inboxes, isLoading: inboxesLoading } = useInboxes(config);
36
+ const { data: messages = [], isLoading: messagesLoading } =
37
+ useInboxMessages(config, selectedInbox?.id ?? null);
38
+ const { data: contact, isLoading: contactLoading } = useGetContact(
39
+ config,
40
+ showContactPanel && selectedInbox?.id_contact
41
+ ? selectedInbox.id_contact
42
+ : null,
43
+ );
44
+
45
+ const sendMessage = useSendMessage(config);
46
+ const retryMessage = useRetryMessage(config);
47
+ const revokeMessage = useRevokeMessage(config);
48
+ const editMessage = useEditMessage(config);
49
+ const updateInbox = useUpdateInbox(config);
50
+ const deleteInbox = useDeleteInbox(config);
51
+
52
+ const handleSend = useCallback(
53
+ (content: string) => {
54
+ if (!selectedInbox) return;
55
+ sendMessage.mutate({ idInbox: selectedInbox.id, content });
56
+ },
57
+ [selectedInbox, sendMessage],
58
+ );
59
+
60
+ const handleStatusChange = useCallback(
61
+ (status: "open" | "pending" | "resolved") => {
62
+ if (!selectedInbox) return;
63
+ updateInbox.mutate({ id: selectedInbox.id, body: { status } });
64
+ },
65
+ [selectedInbox, updateInbox],
66
+ );
67
+
68
+ const handleRevoke = useCallback(
69
+ (message: InboxMessage) => {
70
+ if (!selectedInbox) return;
71
+ revokeMessage.mutate({ id: message.id, idInbox: selectedInbox.id });
72
+ },
73
+ [selectedInbox, revokeMessage],
74
+ );
75
+
76
+ const handleEdit = useCallback(
77
+ (message: InboxMessage, newContent: string) => {
78
+ if (!selectedInbox) return;
79
+ editMessage.mutate({
80
+ id: message.id,
81
+ idInbox: selectedInbox.id,
82
+ content: newContent,
83
+ });
84
+ },
85
+ [selectedInbox, editMessage],
86
+ );
87
+
88
+ return (
89
+ <div className="flex h-full">
90
+ <div className="w-[360px] shrink-0 border-r flex flex-col">
91
+ <InboxSidebar
92
+ inboxes={inboxes}
93
+ isLoading={inboxesLoading}
94
+ selectedInboxId={selectedInbox?.id ?? null}
95
+ onSelectInbox={(inbox) => {
96
+ setSelectedInbox(inbox);
97
+ setShowContactPanel(false);
98
+ }}
99
+ onDeleteInbox={(id) => deleteInbox.mutate(id)}
100
+ filterChannelId={filterChannelId}
101
+ />
102
+ </div>
103
+
104
+ <div className="flex-1 flex min-w-0">
105
+ {selectedInbox ? (
106
+ <>
107
+ <div className="flex-1 flex flex-col min-w-0">
108
+ <ChatView
109
+ messages={messages}
110
+ isLoading={messagesLoading}
111
+ onSend={handleSend}
112
+ onRetry={retryMessage}
113
+ onRevoke={handleRevoke}
114
+ onEdit={handleEdit}
115
+ renderHeader={
116
+ <div className="flex items-center justify-between border-b px-4 py-3">
117
+ <span className="text-sm font-medium">
118
+ {selectedInbox.contact_name || "Conversa"}
119
+ </span>
120
+ <div className="flex items-center gap-2">
121
+ <Select
122
+ value={selectedInbox.status}
123
+ onValueChange={handleStatusChange}
124
+ >
125
+ <SelectTrigger className="w-[130px] h-8 text-xs">
126
+ <SelectValue placeholder="Status" />
127
+ </SelectTrigger>
128
+ <SelectContent>
129
+ <SelectItem value="open">Aberta</SelectItem>
130
+ <SelectItem value="pending">Pendente</SelectItem>
131
+ <SelectItem value="resolved">Resolvida</SelectItem>
132
+ </SelectContent>
133
+ </Select>
134
+ {selectedInbox.id_contact && (
135
+ <Button
136
+ variant="ghost"
137
+ size="icon"
138
+ className="h-8 w-8"
139
+ onClick={() => setShowContactPanel((v) => !v)}
140
+ >
141
+ <User className="h-4 w-4" />
142
+ </Button>
143
+ )}
144
+ </div>
145
+ </div>
146
+ }
147
+ />
148
+ </div>
149
+
150
+ {showContactPanel && selectedInbox.id_contact && (
151
+ <ContactInfoPanel
152
+ contact={contact ?? null}
153
+ isLoading={contactLoading}
154
+ onClose={() => setShowContactPanel(false)}
155
+ />
156
+ )}
157
+ </>
158
+ ) : (
159
+ <div className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-3">
160
+ <MessageCircle className="h-12 w-12 opacity-20" />
161
+ <p className="text-sm">Selecione uma conversa para começar</p>
162
+ </div>
163
+ )}
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -1,11 +1,7 @@
1
1
  import { useState, useMemo } from "react";
2
2
  import type { Inbox } from "../types";
3
3
  import { InboxItem } from "./inbox-item";
4
- import { Input } from "./ui/input";
5
- import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
6
- import { Button } from "./ui/button";
7
- import { Skeleton } from "./ui/skeleton";
8
- import { ScrollArea } from "./ui/scroll-area";
4
+ import { Input, Tabs, TabsList, TabsTrigger, Button, Skeleton, ScrollArea } from "@greatapps/greatauth-ui/ui";
9
5
  import { Search, Plus, Inbox as InboxIcon } from "lucide-react";
10
6
 
11
7
  const STATUS_TABS = [
@@ -1,6 +1,5 @@
1
1
  import { useState } from "react";
2
2
  import type { InboxMessage } from "../types";
3
- import { cn } from "../lib/utils";
4
3
  import { formatMessageTime } from "../utils/format-date";
5
4
  import {
6
5
  Check,
@@ -18,12 +17,11 @@ import {
18
17
  Ban,
19
18
  } from "lucide-react";
20
19
  import {
20
+ cn,
21
21
  DropdownMenu,
22
22
  DropdownMenuContent,
23
23
  DropdownMenuItem,
24
24
  DropdownMenuTrigger,
25
- } from "./ui/dropdown-menu";
26
- import {
27
25
  AlertDialog,
28
26
  AlertDialogAction,
29
27
  AlertDialogCancel,
@@ -32,9 +30,9 @@ import {
32
30
  AlertDialogFooter,
33
31
  AlertDialogHeader,
34
32
  AlertDialogTitle,
35
- } from "./ui/alert-dialog";
36
- import { Button } from "./ui/button";
37
- import { Textarea } from "./ui/textarea";
33
+ Button,
34
+ Textarea,
35
+ } from "@greatapps/greatauth-ui/ui";
38
36
 
39
37
  const statusIcons: Record<string, React.ReactNode> = {
40
38
  pending: <Clock className="h-3 w-3 animate-pulse" />,
@@ -8,23 +8,19 @@ import {
8
8
  DialogTitle,
9
9
  DialogDescription,
10
10
  DialogFooter,
11
- } from "./ui/dialog";
12
- import {
13
11
  Command,
14
12
  CommandInput,
15
13
  CommandList,
16
14
  CommandEmpty,
17
15
  CommandGroup,
18
16
  CommandItem,
19
- } from "./ui/command";
20
- import {
21
17
  Select,
22
18
  SelectContent,
23
19
  SelectItem,
24
20
  SelectTrigger,
25
21
  SelectValue,
26
- } from "./ui/select";
27
- import { Button } from "./ui/button";
22
+ Button,
23
+ } from "@greatapps/greatauth-ui/ui";
28
24
  import { Loader2 } from "lucide-react";
29
25
 
30
26
  export interface NewConversationDialogProps {
@@ -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
+ }