@alfatech/livechat 2024.12.3-1.1
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/.github/workflows/release.yml +33 -0
- package/.husky/pre-commit +2 -0
- package/README.md +30 -0
- package/biome.json +55 -0
- package/index.html +20 -0
- package/package.json +43 -0
- package/postcss.config.cjs +5 -0
- package/public/vite.svg +1 -0
- package/src/api/base.api.ts +118 -0
- package/src/api/error.ts +109 -0
- package/src/assets/react.svg +1 -0
- package/src/components/app/WidgetProvider.tsx +80 -0
- package/src/components/app/button/index.tsx +47 -0
- package/src/components/app/button/types.ts +68 -0
- package/src/components/app/card/profile.tsx +33 -0
- package/src/components/app/icon/index.tsx +144 -0
- package/src/components/app/input/checkbox.tsx +115 -0
- package/src/components/app/input/index.tsx +133 -0
- package/src/components/app/input/label.tsx +33 -0
- package/src/components/app/input/radio.tsx +143 -0
- package/src/components/app/input/select.tsx +54 -0
- package/src/components/app/input/textarea.tsx +68 -0
- package/src/components/app/input/toggle.tsx +162 -0
- package/src/components/app/input/verification.tsx +111 -0
- package/src/features/chat/chat.api.ts +30 -0
- package/src/features/chat/chat.types.ts +50 -0
- package/src/features/chat/partials/ChatCreateForm.tsx +103 -0
- package/src/features/chat/partials/ChatMessageFormFooter.tsx +67 -0
- package/src/features/chat/partials/ChatMessageSection.tsx +103 -0
- package/src/features/widget/components/WidgetIcon.tsx +26 -0
- package/src/features/widget/contexts/page-tracker.tsx +74 -0
- package/src/features/widget/hooks/page-tracker.tsx +57 -0
- package/src/features/widget/widget.api.ts +14 -0
- package/src/features/widget/widget.store.ts +16 -0
- package/src/features/widget/widget.types.ts +28 -0
- package/src/index.css +40 -0
- package/src/layouts/auth.tsx +17 -0
- package/src/layouts/chat.tsx +18 -0
- package/src/lib/cdn.tsx +3 -0
- package/src/lib/cookie.tsx +7 -0
- package/src/lib/debounce.tsx +23 -0
- package/src/lib/hooks/cookie.tsx +22 -0
- package/src/lib/hooks/dayjs.tsx +10 -0
- package/src/lib/hooks/dom.tsx +26 -0
- package/src/lib/hooks/form.tsx +26 -0
- package/src/lib/hooks/router.tsx +31 -0
- package/src/lib/list.tsx +14 -0
- package/src/lib/obj.tsx +23 -0
- package/src/lib/phone.tsx +12 -0
- package/src/main.tsx +31 -0
- package/src/pages/auth/registration-view.tsx +67 -0
- package/src/pages/chat/chat-detail-view.tsx +200 -0
- package/src/router/router.tsx +17 -0
- package/src/router/routes/auth-routes.ts +7 -0
- package/src/router/routes/chat-routes.ts +7 -0
- package/src/router/routes/index.ts +8 -0
- package/src/router/routes/routes.types.ts +0 -0
- package/src/types/i18n.ts +11 -0
- package/src/vite-env.d.ts +1 -0
- package/src/widget.tsx +19 -0
- package/src/ws/events.tsx +69 -0
- package/src/ws/guard.tsx +16 -0
- package/src/ws/hooks.tsx +122 -0
- package/tailwind.config.js +27 -0
- package/tsconfig.app.json +29 -0
- package/tsconfig.app.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +22 -0
- package/tsconfig.node.tsbuildinfo +1 -0
- package/version.cjs +5 -0
- package/vite.config.ts +14 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import ProfileCard from "@/components/app/card/profile";
|
|
2
|
+
import Icon from "@/components/app/icon";
|
|
3
|
+
import WidgetProvider from "@/components/app/WidgetProvider";
|
|
4
|
+
import { chatClear } from "@/features/chat/chat.api";
|
|
5
|
+
import type {
|
|
6
|
+
ChatViewResult,
|
|
7
|
+
MessageListItem,
|
|
8
|
+
} from "@/features/chat/chat.types";
|
|
9
|
+
import ChatMessageFormFooter from "@/features/chat/partials/ChatMessageFormFooter";
|
|
10
|
+
import ChatMessageSection from "@/features/chat/partials/ChatMessageSection";
|
|
11
|
+
import { usePageTrackingWS } from "@/features/widget/hooks/page-tracker";
|
|
12
|
+
import { useWidgetStore } from "@/features/widget/widget.store";
|
|
13
|
+
import WidgetChatLayout from "@/layouts/chat";
|
|
14
|
+
import { useRouter } from "@/lib/hooks/router";
|
|
15
|
+
import { ReceiveEvents, SendEvents } from "@/ws/events";
|
|
16
|
+
import WebsocketGuard from "@/ws/guard";
|
|
17
|
+
import { useWebsocket } from "@/ws/hooks";
|
|
18
|
+
import { useCallback, useEffect, useState } from "react";
|
|
19
|
+
|
|
20
|
+
function ChatDetailView() {
|
|
21
|
+
const { widget, setOpen } = useWidgetStore();
|
|
22
|
+
const socket = useWebsocket();
|
|
23
|
+
const router = useRouter();
|
|
24
|
+
const [chat, setChat] = useState<ChatViewResult | null>(null);
|
|
25
|
+
const [messages, setMessages] = useState<MessageListItem[]>([]);
|
|
26
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
27
|
+
const [closeVisible, setCloseVisible] = useState(false);
|
|
28
|
+
const [shortcutMessages, setShortcutMessages] = useState<MessageListItem[]>(
|
|
29
|
+
[],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const onClose = () => {
|
|
33
|
+
socket?.emit(SendEvents.ChatClose, {});
|
|
34
|
+
setOpen(false);
|
|
35
|
+
chatClear(widget!.client_id);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
socket?.emit(SendEvents.ChatView, {}, (chat) => {
|
|
40
|
+
if (chat === null) {
|
|
41
|
+
setOpen(false);
|
|
42
|
+
chatClear(widget!.client_id);
|
|
43
|
+
router.push("registration");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setChat(chat);
|
|
47
|
+
});
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const addNewMessage = useCallback(
|
|
51
|
+
(msg: MessageListItem) => {
|
|
52
|
+
setMessages([...messages, msg]);
|
|
53
|
+
},
|
|
54
|
+
[messages],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!socket) return;
|
|
59
|
+
socket.on(ReceiveEvents.MessageAgentCreate, (msg) => {
|
|
60
|
+
const m = {
|
|
61
|
+
content: msg.content,
|
|
62
|
+
created_at: msg.created_at,
|
|
63
|
+
files: msg.files,
|
|
64
|
+
id: msg._id,
|
|
65
|
+
sender_type: msg.sender_type,
|
|
66
|
+
answers: msg.answers,
|
|
67
|
+
reply_id: msg.reply_id,
|
|
68
|
+
};
|
|
69
|
+
if (shortcutMessages.length > 0) {
|
|
70
|
+
setShortcutMessages((prev) => [...prev, m]);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (msg.is_shortcut) setShortcutMessages([m]);
|
|
74
|
+
else addNewMessage(m);
|
|
75
|
+
});
|
|
76
|
+
socket.on(ReceiveEvents.ChatUserAssignee, (assignee) => {
|
|
77
|
+
setChat({
|
|
78
|
+
...chat,
|
|
79
|
+
agent_name: assignee.name,
|
|
80
|
+
agent_avatar: assignee.avatar,
|
|
81
|
+
is_uploadable: chat?.is_uploadable ?? false,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
socket.on(ReceiveEvents.MessageAgentTyping, () => {
|
|
85
|
+
setIsTyping(true);
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
socket.off(ReceiveEvents.MessageAgentCreate);
|
|
89
|
+
socket.off(ReceiveEvents.ChatUserAssignee);
|
|
90
|
+
socket.off(ReceiveEvents.MessageAgentTyping);
|
|
91
|
+
};
|
|
92
|
+
}, [addNewMessage]);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!socket) return;
|
|
96
|
+
socket.emit(SendEvents.MessageList, {}, (msgs) => {
|
|
97
|
+
setMessages(msgs);
|
|
98
|
+
});
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
setIsTyping(false);
|
|
104
|
+
}, 1000);
|
|
105
|
+
return () => clearTimeout(timeout);
|
|
106
|
+
}, [isTyping]);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!widget || shortcutMessages.length === 0) return;
|
|
110
|
+
const tmMS =
|
|
111
|
+
(widget.writing_timeout || 650) *
|
|
112
|
+
shortcutMessages[0].content.split(" ").length;
|
|
113
|
+
const timeout = setTimeout(() => {
|
|
114
|
+
addNewMessage(shortcutMessages[0]);
|
|
115
|
+
setShortcutMessages(shortcutMessages.slice(1));
|
|
116
|
+
}, tmMS);
|
|
117
|
+
return () => clearTimeout(timeout);
|
|
118
|
+
}, [shortcutMessages, widget]);
|
|
119
|
+
|
|
120
|
+
if (!chat) return null;
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
{closeVisible && (
|
|
124
|
+
<div className="absolute top-0 left-0 backdrop-blur-sm bg-slate-950/20 z-50 h-full w-full rounded-md">
|
|
125
|
+
<div className="flex flex-col items-center justify-center h-full mx-5">
|
|
126
|
+
<div className="bg-white p-4 rounded-md">
|
|
127
|
+
<div className="text-lg font-semibold text-neutral-900">
|
|
128
|
+
Sohbeti kapatmak istediğinize emin misiniz?
|
|
129
|
+
</div>
|
|
130
|
+
<div className="flex justify-between mt-4">
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
className="bg-red-500 text-white px-4 py-2 rounded-md"
|
|
134
|
+
onClick={onClose}
|
|
135
|
+
>
|
|
136
|
+
Evet
|
|
137
|
+
</button>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
className="bg-neutral-200 text-neutral-900 px-4 py-2 rounded-md"
|
|
141
|
+
onClick={() => setCloseVisible(false)}
|
|
142
|
+
>
|
|
143
|
+
Vazgeç
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
<section className="bg-white h-full w-full rounded-md border flex flex-col">
|
|
152
|
+
<header className="border-b p-4 bg-zink-600 rounded-t-md flex justify-between items-center">
|
|
153
|
+
{chat?.agent_name ? (
|
|
154
|
+
<ProfileCard
|
|
155
|
+
name={chat.agent_name}
|
|
156
|
+
avatar={chat.agent_avatar}
|
|
157
|
+
className="text-neutral-300"
|
|
158
|
+
/>
|
|
159
|
+
) : (
|
|
160
|
+
<div className="text-white">Chat with us</div>
|
|
161
|
+
)}
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
className="text-white"
|
|
165
|
+
onClick={() => setCloseVisible(true)}
|
|
166
|
+
>
|
|
167
|
+
<Icon.X className="w-6 h-6 cursor-pointer" />
|
|
168
|
+
</button>
|
|
169
|
+
</header>
|
|
170
|
+
<ChatMessageSection
|
|
171
|
+
messages={messages}
|
|
172
|
+
isTyping={isTyping || shortcutMessages.length > 0}
|
|
173
|
+
/>
|
|
174
|
+
<ChatMessageFormFooter
|
|
175
|
+
onNewMessage={(msg) => {
|
|
176
|
+
addNewMessage(msg);
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
</section>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function Tracking() {
|
|
185
|
+
usePageTrackingWS();
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default function ChatDetail() {
|
|
190
|
+
return (
|
|
191
|
+
<WebsocketGuard>
|
|
192
|
+
<WidgetProvider defaultOpen>
|
|
193
|
+
<WidgetChatLayout>
|
|
194
|
+
<ChatDetailView />
|
|
195
|
+
</WidgetChatLayout>
|
|
196
|
+
</WidgetProvider>
|
|
197
|
+
<Tracking />
|
|
198
|
+
</WebsocketGuard>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
2
|
+
import { protectedRoutes, publicRoutes } from "./routes";
|
|
3
|
+
|
|
4
|
+
export default function AppRouter() {
|
|
5
|
+
return (
|
|
6
|
+
<MemoryRouter future={{ v7_startTransition: true }}>
|
|
7
|
+
<Routes>
|
|
8
|
+
{protectedRoutes.map((route: any, idx: number) => (
|
|
9
|
+
<Route key={idx} path={route.path} element={<route.component />} />
|
|
10
|
+
))}
|
|
11
|
+
{publicRoutes.map((route: any, idx: number) => (
|
|
12
|
+
<Route path={route.path} key={idx} element={<route.component />} />
|
|
13
|
+
))}
|
|
14
|
+
</Routes>
|
|
15
|
+
</MemoryRouter>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/src/widget.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
import App from "./main.tsx";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
|
|
6
|
+
type Options = {
|
|
7
|
+
container: HTMLElement;
|
|
8
|
+
link_id: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const queryClient = new QueryClient();
|
|
12
|
+
|
|
13
|
+
export default function widget({ container, link_id }: Options) {
|
|
14
|
+
createRoot(container).render(
|
|
15
|
+
<QueryClientProvider client={queryClient}>
|
|
16
|
+
<App link_id={link_id} />
|
|
17
|
+
</QueryClientProvider>,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChatViewResult,
|
|
3
|
+
MessageEntity,
|
|
4
|
+
MessageListItem,
|
|
5
|
+
} from "@/features/chat/chat.types";
|
|
6
|
+
|
|
7
|
+
export enum SendEvents {
|
|
8
|
+
ChatView = "chat:user:view",
|
|
9
|
+
ChatClose = "chat:user:close",
|
|
10
|
+
MessageList = "message:list",
|
|
11
|
+
MessageCreate = "message:create",
|
|
12
|
+
MessageTyping = "message:typing",
|
|
13
|
+
WidgetOpen = "widget:open",
|
|
14
|
+
WidgetClose = "widget:close",
|
|
15
|
+
PageChange = "page-change",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export enum ReceiveEvents {
|
|
19
|
+
MessageAgentCreate = "message:agent:create",
|
|
20
|
+
MessageAgentTyping = "message:agent:typing",
|
|
21
|
+
Banned = "banned",
|
|
22
|
+
ChatUserClose = "chat:user:close",
|
|
23
|
+
ChatUserAssignee = "chat:user:assignee",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ReceiveEventTypes = {
|
|
27
|
+
[ReceiveEvents.MessageAgentCreate]: MessageEntity;
|
|
28
|
+
[ReceiveEvents.MessageAgentTyping]: {};
|
|
29
|
+
[ReceiveEvents.Banned]: {};
|
|
30
|
+
[ReceiveEvents.ChatUserClose]: {};
|
|
31
|
+
[ReceiveEvents.ChatUserAssignee]: {
|
|
32
|
+
chat_id: string;
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
name: string;
|
|
36
|
+
avatar: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type SendEventTypes = {
|
|
41
|
+
[SendEvents.ChatView]: {};
|
|
42
|
+
[SendEvents.MessageList]: {};
|
|
43
|
+
[SendEvents.ChatClose]: {};
|
|
44
|
+
[SendEvents.MessageCreate]: {
|
|
45
|
+
content: string;
|
|
46
|
+
};
|
|
47
|
+
[SendEvents.WidgetOpen]: {};
|
|
48
|
+
[SendEvents.WidgetClose]: {};
|
|
49
|
+
[SendEvents.PageChange]: {
|
|
50
|
+
page: string;
|
|
51
|
+
};
|
|
52
|
+
[SendEvents.MessageTyping]: {
|
|
53
|
+
content: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type EventCallbackTypes = {
|
|
58
|
+
[SendEvents.ChatView]: ChatViewResult;
|
|
59
|
+
[SendEvents.ChatClose]: boolean;
|
|
60
|
+
[SendEvents.MessageList]: MessageListItem[];
|
|
61
|
+
[SendEvents.MessageCreate]: MessageListItem;
|
|
62
|
+
[SendEvents.WidgetOpen]: {};
|
|
63
|
+
[SendEvents.WidgetClose]: {};
|
|
64
|
+
[SendEvents.PageChange]: {};
|
|
65
|
+
[SendEvents.MessageTyping]: {};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type EventCallbackAndSendTypeKeys = keyof EventCallbackTypes &
|
|
69
|
+
keyof SendEventTypes;
|
package/src/ws/guard.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PropsWithChildren, useEffect } from "react";
|
|
2
|
+
import { useWebsocket } from "./hooks";
|
|
3
|
+
|
|
4
|
+
export default function WebsocketGuard({ children }: PropsWithChildren) {
|
|
5
|
+
const socket = useWebsocket();
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!socket) return;
|
|
9
|
+
socket.connect();
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
if (!socket || !socket.connected) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return children;
|
|
16
|
+
}
|
package/src/ws/hooks.tsx
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createContext, useContext, useState } from "react";
|
|
2
|
+
import { type Socket, io } from "socket.io-client";
|
|
3
|
+
import type {
|
|
4
|
+
EventCallbackAndSendTypeKeys,
|
|
5
|
+
EventCallbackTypes,
|
|
6
|
+
ReceiveEventTypes,
|
|
7
|
+
SendEventTypes,
|
|
8
|
+
} from "./events";
|
|
9
|
+
import { getCookieVal } from "@/lib/cookie";
|
|
10
|
+
|
|
11
|
+
type EventHandler<T = any> = (e: T) => void;
|
|
12
|
+
|
|
13
|
+
type WebSocketContext = {
|
|
14
|
+
socket: Socket | null;
|
|
15
|
+
connected: boolean;
|
|
16
|
+
events: Record<string, EventHandler>;
|
|
17
|
+
connect: () => void;
|
|
18
|
+
reconnect: () => void;
|
|
19
|
+
disconnect: () => void;
|
|
20
|
+
on: <T extends keyof ReceiveEventTypes>(
|
|
21
|
+
event: T,
|
|
22
|
+
handler: EventHandler<ReceiveEventTypes[T]>,
|
|
23
|
+
) => void;
|
|
24
|
+
off: <T extends keyof ReceiveEventTypes>(event: T) => void;
|
|
25
|
+
emit: <T extends keyof SendEventTypes>(
|
|
26
|
+
event: T,
|
|
27
|
+
data: SendEventTypes[T],
|
|
28
|
+
callback?: (data: EventCallbackTypes[T]) => void,
|
|
29
|
+
) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const websocketContext = createContext<WebSocketContext | null>(null);
|
|
33
|
+
|
|
34
|
+
export const useWebsocket = () => {
|
|
35
|
+
return useContext(websocketContext);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const WebSocketProvider = ({ children }: React.PropsWithChildren) => {
|
|
39
|
+
const [socket, setSocket] = useState<Socket | null>(null);
|
|
40
|
+
const [connected, setConnected] = useState<boolean>(false);
|
|
41
|
+
const [events, setEvents] = useState<Record<string, EventHandler>>({});
|
|
42
|
+
|
|
43
|
+
const connect = () => {
|
|
44
|
+
const s = io(import.meta.env.VITE_WS_URL as string, {
|
|
45
|
+
transports: ["websocket"],
|
|
46
|
+
auth: {
|
|
47
|
+
customer_token: getCookieVal("customer_token"),
|
|
48
|
+
device_id: getCookieVal("device_id"),
|
|
49
|
+
customer_refresh_token: getCookieVal("customer_refresh_token"),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
s.on("error", (err) => {
|
|
53
|
+
console.error("Websocket error", err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
s.on("connect", () => {
|
|
57
|
+
setSocket(s);
|
|
58
|
+
setConnected(true);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const reconnect = () => {
|
|
63
|
+
if (socket) {
|
|
64
|
+
socket.connect();
|
|
65
|
+
setConnected(true);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const disconnect = () => {
|
|
70
|
+
if (socket) {
|
|
71
|
+
socket.disconnect();
|
|
72
|
+
setConnected(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const on = <T extends keyof ReceiveEventTypes>(
|
|
77
|
+
event: T,
|
|
78
|
+
handler: EventHandler<ReceiveEventTypes[T]>,
|
|
79
|
+
) => {
|
|
80
|
+
if (socket) {
|
|
81
|
+
socket.on(event as any, (args: any) => {
|
|
82
|
+
handler(args);
|
|
83
|
+
});
|
|
84
|
+
setEvents({ ...events, [event]: handler });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const off = <T extends keyof ReceiveEventTypes>(event: T) => {
|
|
89
|
+
if (socket && events[event]) {
|
|
90
|
+
socket.off(event, events[event] as any);
|
|
91
|
+
delete events[event];
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const emit = <T extends EventCallbackAndSendTypeKeys>(
|
|
96
|
+
event: T,
|
|
97
|
+
data: SendEventTypes[T],
|
|
98
|
+
callback?: (data: EventCallbackTypes[T]) => void,
|
|
99
|
+
) => {
|
|
100
|
+
if (socket) {
|
|
101
|
+
socket.emit(event, data, callback);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<websocketContext.Provider
|
|
107
|
+
value={{
|
|
108
|
+
socket,
|
|
109
|
+
connected,
|
|
110
|
+
events,
|
|
111
|
+
connect,
|
|
112
|
+
reconnect,
|
|
113
|
+
disconnect,
|
|
114
|
+
on,
|
|
115
|
+
off,
|
|
116
|
+
emit,
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</websocketContext.Provider>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
|
4
|
+
theme: {
|
|
5
|
+
extend: {
|
|
6
|
+
width: {
|
|
7
|
+
86: "22.5rem",
|
|
8
|
+
},
|
|
9
|
+
colors: {
|
|
10
|
+
zink: {
|
|
11
|
+
50: "#E2EAF3",
|
|
12
|
+
100: "#C8D7E9",
|
|
13
|
+
200: "#92AFD3",
|
|
14
|
+
300: "#5885BC",
|
|
15
|
+
400: "#395F8E",
|
|
16
|
+
500: "#233A57",
|
|
17
|
+
600: "#1C2E45",
|
|
18
|
+
700: "#132337",
|
|
19
|
+
800: "#0F1824",
|
|
20
|
+
900: "#070C12",
|
|
21
|
+
950: "#030507",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
plugins: [],
|
|
27
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["./src/*"],
|
|
18
|
+
"~/*": ["/*"]
|
|
19
|
+
},
|
|
20
|
+
"baseUrl": ".",
|
|
21
|
+
|
|
22
|
+
/* Linting */
|
|
23
|
+
"strict": true,
|
|
24
|
+
"noUnusedLocals": true,
|
|
25
|
+
"noUnusedParameters": true,
|
|
26
|
+
"noFallthroughCasesInSwitch": true
|
|
27
|
+
},
|
|
28
|
+
"include": ["src"]
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/widget.tsx","./src/api/base.api.ts","./src/api/error.ts","./src/components/app/WidgetProvider.tsx","./src/components/app/button/index.tsx","./src/components/app/button/types.ts","./src/components/app/card/profile.tsx","./src/components/app/icon/index.tsx","./src/components/app/input/checkbox.tsx","./src/components/app/input/index.tsx","./src/components/app/input/label.tsx","./src/components/app/input/radio.tsx","./src/components/app/input/select.tsx","./src/components/app/input/textarea.tsx","./src/components/app/input/toggle.tsx","./src/components/app/input/verification.tsx","./src/features/chat/chat.api.ts","./src/features/chat/chat.types.ts","./src/features/chat/partials/ChatCreateForm.tsx","./src/features/chat/partials/ChatMessageFormFooter.tsx","./src/features/chat/partials/ChatMessageSection.tsx","./src/features/widget/widget.api.ts","./src/features/widget/widget.store.ts","./src/features/widget/widget.types.ts","./src/features/widget/components/WidgetIcon.tsx","./src/features/widget/contexts/page-tracker.tsx","./src/features/widget/hooks/page-tracker.tsx","./src/layouts/auth.tsx","./src/layouts/chat.tsx","./src/lib/cdn.tsx","./src/lib/cookie.tsx","./src/lib/debounce.tsx","./src/lib/list.tsx","./src/lib/obj.tsx","./src/lib/phone.tsx","./src/lib/hooks/cookie.tsx","./src/lib/hooks/dayjs.tsx","./src/lib/hooks/dom.tsx","./src/lib/hooks/form.tsx","./src/lib/hooks/router.tsx","./src/pages/auth/registration-view.tsx","./src/pages/chat/chat-detail-view.tsx","./src/router/router.tsx","./src/router/routes/auth-routes.ts","./src/router/routes/chat-routes.ts","./src/router/routes/index.ts","./src/router/routes/routes.types.ts","./src/types/i18n.ts","./src/ws/events.tsx","./src/ws/guard.tsx","./src/ws/hooks.tsx"],"version":"5.7.2"}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
|
|
8
|
+
/* Bundler mode */
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
|
|
15
|
+
/* Linting */
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["vite.config.ts"]
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./vite.config.ts"],"version":"5.7.2"}
|
package/version.cjs
ADDED
package/vite.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react-swc";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
// https://vitejs.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
"@": resolve(__dirname, "src"),
|
|
11
|
+
"~": resolve(__dirname),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|