@zenify-omni/chat-js 0.1.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.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/api/campaign.js +19 -0
- package/api/chat.js +518 -0
- package/api/chatroom.js +254 -0
- package/api/contact.js +45 -0
- package/api/crm-organization.js +101 -0
- package/api/customer.js +143 -0
- package/api/facebook-labels.js +11 -0
- package/api/field.js +11 -0
- package/api/file.js +17 -0
- package/api/filter.js +50 -0
- package/api/group.js +11 -0
- package/api/iam.js +23 -0
- package/api/index.js +54 -0
- package/api/orgTree.js +45 -0
- package/api/post.js +111 -0
- package/api/queue.js +15 -0
- package/api/report.js +422 -0
- package/api/send.js +21 -0
- package/api/session-event.js +30 -0
- package/api/session.js +249 -0
- package/api/settings/assign-task.js +10 -0
- package/api/settings/channel.js +136 -0
- package/api/settings/chat-layout.js +37 -0
- package/api/settings/chat-timeout.js +10 -0
- package/api/settings/common.js +32 -0
- package/api/settings/config.js +19 -0
- package/api/settings/index.js +24 -0
- package/api/settings/ivr.js +25 -0
- package/api/settings/queue-v3.js +56 -0
- package/api/settings/queue.js +48 -0
- package/api/settings/routing.js +32 -0
- package/api/settings/service.js +31 -0
- package/api/settings.js +567 -0
- package/api/shipping.js +19 -0
- package/api/shopee.js +21 -0
- package/api/smart-qc/index.js +4 -0
- package/api/smart-qc/keyword.js +10 -0
- package/api/tag.js +11 -0
- package/api/user.js +177 -0
- package/client.js +391 -0
- package/config/env.js +76 -0
- package/constants/advance-filter.js +358 -0
- package/constants/auto-assign-tag.js +13 -0
- package/constants/chat-message.js +423 -0
- package/constants/chat-room-tag.js +7 -0
- package/constants/chat-source.js +68 -0
- package/constants/chatbot.js +58 -0
- package/constants/common.js +4 -0
- package/constants/crm-organization.js +75 -0
- package/constants/distribute.js +8 -0
- package/constants/distribution-log.js +14 -0
- package/constants/events.js +27 -0
- package/constants/file-upload.js +86 -0
- package/constants/lead.js +111 -0
- package/constants/mask-data-config.js +16 -0
- package/constants/month.js +78 -0
- package/constants/quick-filter.js +49 -0
- package/constants/regex.js +34 -0
- package/constants/setting.js +62 -0
- package/constants/shipping.js +12 -0
- package/constants/social.js +21 -0
- package/constants/sortable-fields.js +146 -0
- package/constants/tenant_config.js +154 -0
- package/constants/use-query-key.js +470 -0
- package/constants/user.js +293 -0
- package/exceptions/axios-error.js +24 -0
- package/exceptions/https-error.js +28 -0
- package/exceptions/validator-error.js +20 -0
- package/lib/avatar-color.js +31 -0
- package/lib/i18n/index.js +85 -0
- package/lib/i18n/language-store.js +74 -0
- package/lib/i18n/locales/en/campaign.json +3 -0
- package/lib/i18n/locales/en/chat.json +656 -0
- package/lib/i18n/locales/en/common.json +1846 -0
- package/lib/i18n/locales/en/customer.json +20 -0
- package/lib/i18n/locales/en/lead.json +5 -0
- package/lib/i18n/locales/en/lumi.json +4 -0
- package/lib/i18n/locales/en/notification.json +107 -0
- package/lib/i18n/locales/en/org.json +4 -0
- package/lib/i18n/locales/en/posts.json +8 -0
- package/lib/i18n/locales/en/settings.json +39 -0
- package/lib/i18n/locales/vi/campaign.json +3 -0
- package/lib/i18n/locales/vi/chat.json +660 -0
- package/lib/i18n/locales/vi/common.json +1846 -0
- package/lib/i18n/locales/vi/customer.json +20 -0
- package/lib/i18n/locales/vi/lead.json +5 -0
- package/lib/i18n/locales/vi/lumi.json +4 -0
- package/lib/i18n/locales/vi/notification.json +107 -0
- package/lib/i18n/locales/vi/org.json +4 -0
- package/lib/i18n/locales/vi/posts.json +8 -0
- package/lib/i18n/locales/vi/settings.json +39 -0
- package/lib/i18n/server-label.js +33 -0
- package/lib/phone.js +70 -0
- package/lib/user-avatar.js +21 -0
- package/package.json +45 -0
- package/socket/index.js +30 -0
- package/transport-registry.js +77 -0
- package/utils/access-detail.js +119 -0
- package/utils/auth.js +205 -0
- package/utils/chunk-reload-guard.js +4 -0
- package/utils/concurrency-limiter.js +30 -0
- package/utils/crm-organization.js +0 -0
- package/utils/currency.js +14 -0
- package/utils/customers.js +42 -0
- package/utils/data.js +178 -0
- package/utils/date.js +144 -0
- package/utils/debounced-invalidate.js +15 -0
- package/utils/emoticon.js +134 -0
- package/utils/export.js +7 -0
- package/utils/field-layout.js +33 -0
- package/utils/filter-adapters.js +9 -0
- package/utils/filter.js +18 -0
- package/utils/formatters.js +6 -0
- package/utils/image-compression.js +169 -0
- package/utils/leads.js +13 -0
- package/utils/microlink.js +20 -0
- package/utils/org-tree.js +108 -0
- package/utils/request.js +461 -0
- package/utils/session-end.js +19 -0
- package/utils/string.js +154 -0
- package/utils/table-display-config.js +80 -0
- package/utils/validation.js +49 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import i18n from "../lib/i18n/index.js";
|
|
2
|
+
const UserRole = Object.freeze({
|
|
3
|
+
// customer
|
|
4
|
+
END_USER: "end_user",
|
|
5
|
+
// user
|
|
6
|
+
AGENT: "agent",
|
|
7
|
+
TEAM_LEADER: "team_leader",
|
|
8
|
+
SUPERVISOR: "supervisor",
|
|
9
|
+
ADMINISTRATOR: "administrator",
|
|
10
|
+
// sale
|
|
11
|
+
SALE: "sale",
|
|
12
|
+
SALE_TEAM_LEADER: "sale_team_leader",
|
|
13
|
+
// Chatbot
|
|
14
|
+
CHATBOT: "chatbot"
|
|
15
|
+
});
|
|
16
|
+
const UserRoleContext = Object.freeze({
|
|
17
|
+
get [UserRole.END_USER]() {
|
|
18
|
+
return i18n.t("common:label.customer", {
|
|
19
|
+
defaultValue: "Kh\xE1ch h\xE0ng"
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
get [UserRole.AGENT]() {
|
|
23
|
+
return i18n.t("common:label.agent", {
|
|
24
|
+
defaultValue: "Chuy\xEAn vi\xEAn"
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
get [UserRole.TEAM_LEADER]() {
|
|
28
|
+
return i18n.t("common:label.departmentHead", {
|
|
29
|
+
defaultValue: "Tr\u01B0\u1EDFng b\u1ED9 ph\u1EADn"
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
[UserRole.SUPERVISOR]: "Supervisor",
|
|
33
|
+
[UserRole.ADMINISTRATOR]: "Administrator",
|
|
34
|
+
// sale
|
|
35
|
+
[UserRole.SALE]: "Sale",
|
|
36
|
+
get [UserRole.SALE_TEAM_LEADER]() {
|
|
37
|
+
return i18n.t("common:label.salesDepartmentHead", {
|
|
38
|
+
defaultValue: "Tr\u01B0\u1EDFng b\u1ED9 ph\u1EADn Sale"
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
// Chatbot
|
|
42
|
+
[UserRole.CHATBOT]: "AI Agent"
|
|
43
|
+
});
|
|
44
|
+
const AssigneeRoles = Object.freeze([
|
|
45
|
+
UserRole.ADMINISTRATOR,
|
|
46
|
+
UserRole.AGENT,
|
|
47
|
+
UserRole.SUPERVISOR,
|
|
48
|
+
UserRole.TEAM_LEADER
|
|
49
|
+
]);
|
|
50
|
+
const SeniorStaffRoles = Object.freeze([
|
|
51
|
+
UserRole.ADMINISTRATOR,
|
|
52
|
+
UserRole.SUPERVISOR,
|
|
53
|
+
UserRole.TEAM_LEADER,
|
|
54
|
+
UserRole.SALE_TEAM_LEADER
|
|
55
|
+
]);
|
|
56
|
+
const CustomerType = Object.freeze({
|
|
57
|
+
ECONOMY: "0",
|
|
58
|
+
SILVER: "1",
|
|
59
|
+
GOLD: "2",
|
|
60
|
+
DIAMOND: "3"
|
|
61
|
+
});
|
|
62
|
+
const CustomerTypeContext = Object.freeze({
|
|
63
|
+
get [CustomerType.ECONOMY]() {
|
|
64
|
+
return i18n.t("common:label.rankRegular", {
|
|
65
|
+
defaultValue: "Th\u01B0\u1EDDng"
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
get [CustomerType.SILVER]() {
|
|
69
|
+
return i18n.t("common:label.silver", { defaultValue: "B\u1EA1c" });
|
|
70
|
+
},
|
|
71
|
+
get [CustomerType.GOLD]() {
|
|
72
|
+
return i18n.t("common:label.rankGold", {
|
|
73
|
+
defaultValue: "V\xE0ng"
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
get [CustomerType.DIAMOND]() {
|
|
77
|
+
return i18n.t("common:label.rankDiamond", {
|
|
78
|
+
defaultValue: "Kim c\u01B0\u01A1ng"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const SourceUser = Object.freeze({
|
|
83
|
+
WEB: "web",
|
|
84
|
+
CALL_IN: "CALL_INBOUND",
|
|
85
|
+
CALL_OUT: "CALL_OUTBOUND",
|
|
86
|
+
CALL: "call",
|
|
87
|
+
CHAT: "chat",
|
|
88
|
+
ZALO: "zalo",
|
|
89
|
+
FACEBOOK: "facebook",
|
|
90
|
+
EMAIL: "email",
|
|
91
|
+
HAND: "hand",
|
|
92
|
+
API: "api",
|
|
93
|
+
IMPORT: "import",
|
|
94
|
+
WHATSAPP: "whatsapp",
|
|
95
|
+
TIKTOK: "tiktok",
|
|
96
|
+
TIKTOK_SHOP: "tiktok_shop",
|
|
97
|
+
INSTAGRAM: "instagram",
|
|
98
|
+
SHOPEE: "shopee"
|
|
99
|
+
});
|
|
100
|
+
const SourceUserContext = Object.freeze({
|
|
101
|
+
[SourceUser.WEB]: "Livechat",
|
|
102
|
+
get [SourceUser.CALL_OUT]() {
|
|
103
|
+
return i18n.t("common:label.callOut", {
|
|
104
|
+
defaultValue: "G\u1ECDi ra"
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
get [SourceUser.CALL_IN]() {
|
|
108
|
+
return i18n.t("common:label.callIn", {
|
|
109
|
+
defaultValue: "G\u1ECDi v\xE0o"
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
[SourceUser.CHAT]: "Livechat",
|
|
113
|
+
[SourceUser.ZALO]: "Zalo",
|
|
114
|
+
[SourceUser.FACEBOOK]: "Facebook",
|
|
115
|
+
[SourceUser.EMAIL]: "Email",
|
|
116
|
+
get [SourceUser.HAND]() {
|
|
117
|
+
return i18n.t("common:label.manual", {
|
|
118
|
+
defaultValue: "Th\u1EE7 c\xF4ng"
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
[SourceUser.API]: "Api",
|
|
122
|
+
[SourceUser.IMPORT]: "Import",
|
|
123
|
+
[SourceUser.WHATSAPP]: "WhatsApp",
|
|
124
|
+
[SourceUser.TIKTOK]: "Tiktok Business",
|
|
125
|
+
[SourceUser.TIKTOK_SHOP]: "Tiktok Shop",
|
|
126
|
+
[SourceUser.INSTAGRAM]: "Instagram",
|
|
127
|
+
[SourceUser.SHOPEE]: "Shopee"
|
|
128
|
+
});
|
|
129
|
+
const CustomerAssign = Object.freeze({
|
|
130
|
+
ALL: "all",
|
|
131
|
+
MyOrganization: "my_organization",
|
|
132
|
+
MyGroup: "my_group",
|
|
133
|
+
MyCustomer: "me"
|
|
134
|
+
});
|
|
135
|
+
const Gender = Object.freeze({
|
|
136
|
+
MALE: "male",
|
|
137
|
+
FEMALE: "female",
|
|
138
|
+
OTHER: "other"
|
|
139
|
+
});
|
|
140
|
+
const GenderContext = Object.freeze({
|
|
141
|
+
[Gender.MALE]: "Nam",
|
|
142
|
+
get [Gender.FEMALE]() {
|
|
143
|
+
return i18n.t("common:label.female", { defaultValue: "N\u1EEF" });
|
|
144
|
+
},
|
|
145
|
+
get [Gender.OTHER]() {
|
|
146
|
+
return i18n.t("common:label.other", { defaultValue: "Kh\xE1c" });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const GenderOptions = [
|
|
150
|
+
{ id: Gender.MALE, text: GenderContext[Gender.MALE] },
|
|
151
|
+
{ id: Gender.FEMALE, text: GenderContext[Gender.FEMALE] },
|
|
152
|
+
{ id: Gender.OTHER, text: GenderContext[Gender.OTHER] }
|
|
153
|
+
];
|
|
154
|
+
const SourceUserOptions = [
|
|
155
|
+
{
|
|
156
|
+
text: SourceUserContext[SourceUser.HAND],
|
|
157
|
+
id: SourceUser.HAND
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
text: SourceUserContext[SourceUser.WEB],
|
|
161
|
+
id: SourceUser.WEB
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
text: SourceUserContext[SourceUser.CALL_OUT],
|
|
165
|
+
id: SourceUser.CALL_OUT
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
text: SourceUserContext[SourceUser.CALL_IN],
|
|
169
|
+
id: SourceUser.CALL_IN
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
text: SourceUserContext[SourceUser.ZALO],
|
|
173
|
+
id: SourceUser.ZALO
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
text: SourceUserContext[SourceUser.FACEBOOK],
|
|
177
|
+
id: SourceUser.FACEBOOK
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
text: SourceUserContext[SourceUser.EMAIL],
|
|
181
|
+
id: SourceUser.EMAIL
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
text: SourceUserContext[SourceUser.WHATSAPP],
|
|
185
|
+
id: SourceUser.WHATSAPP
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
text: SourceUserContext[SourceUser.API],
|
|
189
|
+
id: SourceUser.API
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
text: SourceUserContext[SourceUser.IMPORT],
|
|
193
|
+
id: SourceUser.IMPORT
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
text: SourceUserContext[SourceUser.TIKTOK],
|
|
197
|
+
id: SourceUser.TIKTOK
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
text: SourceUserContext[SourceUser.INSTAGRAM],
|
|
201
|
+
id: SourceUser.INSTAGRAM
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
text: SourceUserContext[SourceUser.SHOPEE],
|
|
205
|
+
id: SourceUser.SHOPEE
|
|
206
|
+
}
|
|
207
|
+
];
|
|
208
|
+
const CrmType = Object.freeze({
|
|
209
|
+
Contact: "contact",
|
|
210
|
+
Lead: "lead",
|
|
211
|
+
Internal: "internal"
|
|
212
|
+
// used by internal call only
|
|
213
|
+
});
|
|
214
|
+
const DuplicateContactWarningType = Object.freeze({
|
|
215
|
+
Phone: "DUPLICATE_PHONE",
|
|
216
|
+
CustomerCode: "DUPLICATE_CUSTOMER_CODE"
|
|
217
|
+
});
|
|
218
|
+
const isDuplicateContactWarning = (type) => type === DuplicateContactWarningType.Phone || type === DuplicateContactWarningType.CustomerCode;
|
|
219
|
+
const ConversionSource = Object.freeze({
|
|
220
|
+
IMPORT_FILE: "import_file",
|
|
221
|
+
FROM_LEAD: "from_lead",
|
|
222
|
+
MANUAL: "manual",
|
|
223
|
+
WORKFLOW: "workflow",
|
|
224
|
+
API: "api",
|
|
225
|
+
SYSTEM: "system"
|
|
226
|
+
});
|
|
227
|
+
const ConversionSourceContext = Object.freeze({
|
|
228
|
+
[ConversionSource.IMPORT_FILE]: "Import file",
|
|
229
|
+
get [ConversionSource.FROM_LEAD]() {
|
|
230
|
+
return i18n.t("common:label.convertedFromLead", {
|
|
231
|
+
defaultValue: "Chuy\u1EC3n t\u1EEB ti\u1EC1m n\u0103ng"
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
get [ConversionSource.MANUAL]() {
|
|
235
|
+
return i18n.t("common:label.manual", {
|
|
236
|
+
defaultValue: "Th\u1EE7 c\xF4ng"
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
[ConversionSource.WORKFLOW]: "Workflow",
|
|
240
|
+
[ConversionSource.API]: "API",
|
|
241
|
+
get [ConversionSource.SYSTEM]() {
|
|
242
|
+
return i18n.t("common:label.system", {
|
|
243
|
+
defaultValue: "H\u1EC7 th\u1ED1ng"
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const ConversionSourceOptions = [
|
|
248
|
+
{
|
|
249
|
+
id: ConversionSource.IMPORT_FILE,
|
|
250
|
+
text: ConversionSourceContext[ConversionSource.IMPORT_FILE]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: ConversionSource.FROM_LEAD,
|
|
254
|
+
text: ConversionSourceContext[ConversionSource.FROM_LEAD]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: ConversionSource.MANUAL,
|
|
258
|
+
text: ConversionSourceContext[ConversionSource.MANUAL]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: ConversionSource.WORKFLOW,
|
|
262
|
+
text: ConversionSourceContext[ConversionSource.WORKFLOW]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: ConversionSource.API,
|
|
266
|
+
text: ConversionSourceContext[ConversionSource.API]
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: ConversionSource.SYSTEM,
|
|
270
|
+
text: ConversionSourceContext[ConversionSource.SYSTEM]
|
|
271
|
+
}
|
|
272
|
+
];
|
|
273
|
+
export {
|
|
274
|
+
AssigneeRoles,
|
|
275
|
+
ConversionSource,
|
|
276
|
+
ConversionSourceContext,
|
|
277
|
+
ConversionSourceOptions,
|
|
278
|
+
CrmType,
|
|
279
|
+
CustomerAssign,
|
|
280
|
+
CustomerType,
|
|
281
|
+
CustomerTypeContext,
|
|
282
|
+
DuplicateContactWarningType,
|
|
283
|
+
Gender,
|
|
284
|
+
GenderContext,
|
|
285
|
+
GenderOptions,
|
|
286
|
+
SeniorStaffRoles,
|
|
287
|
+
SourceUser,
|
|
288
|
+
SourceUserContext,
|
|
289
|
+
SourceUserOptions,
|
|
290
|
+
UserRole,
|
|
291
|
+
UserRoleContext,
|
|
292
|
+
isDuplicateContactWarning
|
|
293
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import i18n from "../lib/i18n/index.js";
|
|
2
|
+
const ErrorMessage = Object.freeze({
|
|
3
|
+
ECONNABORTED: "Request b\u1ECB timeout",
|
|
4
|
+
ERR_NETWORK: "L\u1ED7i k\u1EBFt n\u1ED1i m\u1EA1ng",
|
|
5
|
+
ERR_BAD_REQUEST: "D\u1EEF li\u1EC7u request kh\xF4ng h\u1EE3p l\u1EC7",
|
|
6
|
+
ERR_BAD_RESPONSE: "Response kh\xF4ng h\u1EE3p l\u1EC7",
|
|
7
|
+
ERR_FR_TOO_MANY_REDIRECTS: "Qu\xE1 nhi\u1EC1u redirect",
|
|
8
|
+
ERR_CANCELED: "Request b\u1ECB h\u1EE7y",
|
|
9
|
+
ERR_INVALID_URL: "URL kh\xF4ng h\u1EE3p l\u1EC7"
|
|
10
|
+
});
|
|
11
|
+
class AxiosError {
|
|
12
|
+
constructor({ code, message = "" }) {
|
|
13
|
+
return {
|
|
14
|
+
error: true,
|
|
15
|
+
message: message || ErrorMessage[code] || i18n.t("settings:user.unknownError", {
|
|
16
|
+
defaultValue: "L\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh"
|
|
17
|
+
}),
|
|
18
|
+
code
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
AxiosError
|
|
24
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import i18n from "../lib/i18n/index.js";
|
|
2
|
+
const ErrorMessage = Object.freeze({
|
|
3
|
+
400: "D\u1EEF li\u1EC7u g\u1EEDi l\xEAn kh\xF4ng h\u1EE3p l\u1EC7",
|
|
4
|
+
401: "B\u1EA1n kh\xF4ng c\xF3 quy\u1EC1n truy c\u1EADp",
|
|
5
|
+
409: "\u0110\xE3 x\u1EA3y ra xung \u0111\u1ED9t, vui l\xF2ng th\u1EED l\u1EA1i sau!",
|
|
6
|
+
403: "B\u1EA1n kh\xF4ng c\xF3 quy\u1EC1n truy c\u1EADp",
|
|
7
|
+
404: "Ki\u1EC3m tra URL ho\u1EB7c endpoint c\xF3 \u0111\xFAng kh\xF4ng!",
|
|
8
|
+
408: "Th\u1EDDi gian k\u1EBFt n\u1ED1i \u0111\u1EBFn server qu\xE1 l\xE2u, vui l\xF2ng ki\u1EC3m tra k\u1EBFt n\u1ED1i m\u1EA1ng!",
|
|
9
|
+
429: "G\u1EEDi qu\xE1 nhi\u1EC1u y\xEAu c\u1EA7u \u0111\u1ED3ng th\u1EDDi, vui l\xF2ng th\u1EED l\u1EA1i sau \xEDt ph\xFAt!",
|
|
10
|
+
500: "\u0110\xE3 x\u1EA3y ra l\u1ED7i t\u1EEB server, vui l\xF2ng th\u1EED l\u1EA1i sau!",
|
|
11
|
+
503: "D\u1ECBch v\u1EE5 t\u1EA1m th\u1EDDi kh\xF4ng ho\u1EA1t \u0111\u1ED9ng, vui l\xF2ng th\u1EED l\u1EA1i sau!",
|
|
12
|
+
504: "Server \u0111ang b\u1EADn, vui l\xF2ng th\u1EED l\u1EA1i sau!"
|
|
13
|
+
});
|
|
14
|
+
class HttpsError {
|
|
15
|
+
constructor({ status, message, data }) {
|
|
16
|
+
return {
|
|
17
|
+
error: true,
|
|
18
|
+
status,
|
|
19
|
+
data,
|
|
20
|
+
message: message || ErrorMessage[status] || i18n.t("common:label.anErrorOccurredDuringProcessing", {
|
|
21
|
+
defaultValue: "\u0110\xE3 x\u1EA3y ra l\u1ED7i trong qu\xE1 tr\xECnh th\u1EF1c hi\u1EC7n"
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
HttpsError
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class ValidatorError {
|
|
2
|
+
constructor({ validationErrors, message }) {
|
|
3
|
+
const errors = {};
|
|
4
|
+
if (validationErrors && validationErrors.length) {
|
|
5
|
+
validationErrors.filter((v) => v).forEach((err) => {
|
|
6
|
+
errors[err["param"]] = err["message"] || `${err["param"]} field is not a valid`;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
error: true,
|
|
11
|
+
data: errors,
|
|
12
|
+
message: message || "Validation Failed",
|
|
13
|
+
statusCode: 422,
|
|
14
|
+
isInvalid: true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
ValidatorError
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const AVATAR_PALETTE = [
|
|
2
|
+
"#0EA5E9",
|
|
3
|
+
// sky
|
|
4
|
+
"#16A34A",
|
|
5
|
+
// green
|
|
6
|
+
"#F59E0B",
|
|
7
|
+
// amber
|
|
8
|
+
"#EC4899",
|
|
9
|
+
// pink
|
|
10
|
+
"#8B5CF6",
|
|
11
|
+
// violet
|
|
12
|
+
"#EF4444",
|
|
13
|
+
// red
|
|
14
|
+
"#06B6D4",
|
|
15
|
+
// cyan
|
|
16
|
+
"#F97316",
|
|
17
|
+
// orange
|
|
18
|
+
"#6366F1",
|
|
19
|
+
// indigo
|
|
20
|
+
"#14B8A6"
|
|
21
|
+
// teal
|
|
22
|
+
];
|
|
23
|
+
function customerInitials(name) {
|
|
24
|
+
const parts = String(name ?? "").trim().split(/\s+/).filter(Boolean);
|
|
25
|
+
if (parts.length === 0) return "?";
|
|
26
|
+
if (parts.length === 1) return parts[0][0].toUpperCase();
|
|
27
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
customerInitials
|
|
31
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
import LanguageDetector from "i18next-browser-languagedetector";
|
|
3
|
+
import ICU from "i18next-icu";
|
|
4
|
+
import resourcesToBackend from "i18next-resources-to-backend";
|
|
5
|
+
const SUPPORTED_LANGUAGES = ["vi", "en"];
|
|
6
|
+
function resolveFallback(code) {
|
|
7
|
+
return code === "vi" ? false : "en";
|
|
8
|
+
}
|
|
9
|
+
const enLoaders = {
|
|
10
|
+
"campaign": () => import("./locales/en/campaign.json"),
|
|
11
|
+
"chat": () => import("./locales/en/chat.json"),
|
|
12
|
+
"common": () => import("./locales/en/common.json"),
|
|
13
|
+
"customer": () => import("./locales/en/customer.json"),
|
|
14
|
+
"lead": () => import("./locales/en/lead.json"),
|
|
15
|
+
"lumi": () => import("./locales/en/lumi.json"),
|
|
16
|
+
"notification": () => import("./locales/en/notification.json"),
|
|
17
|
+
"org": () => import("./locales/en/org.json"),
|
|
18
|
+
"posts": () => import("./locales/en/posts.json"),
|
|
19
|
+
"settings": () => import("./locales/en/settings.json")
|
|
20
|
+
};
|
|
21
|
+
const viLoaders = {
|
|
22
|
+
"campaign": () => import("./locales/vi/campaign.json"),
|
|
23
|
+
"chat": () => import("./locales/vi/chat.json"),
|
|
24
|
+
"common": () => import("./locales/vi/common.json"),
|
|
25
|
+
"customer": () => import("./locales/vi/customer.json"),
|
|
26
|
+
"lead": () => import("./locales/vi/lead.json"),
|
|
27
|
+
"lumi": () => import("./locales/vi/lumi.json"),
|
|
28
|
+
"notification": () => import("./locales/vi/notification.json"),
|
|
29
|
+
"org": () => import("./locales/vi/org.json"),
|
|
30
|
+
"posts": () => import("./locales/vi/posts.json"),
|
|
31
|
+
"settings": () => import("./locales/vi/settings.json")
|
|
32
|
+
};
|
|
33
|
+
const loaders = { en: enLoaders, vi: viLoaders };
|
|
34
|
+
const NS = Object.keys(enLoaders).filter((ns) => ns !== "translation").sort();
|
|
35
|
+
async function loadNamespacesForLanguage(lng, namespaces) {
|
|
36
|
+
await i18n.reloadResources([lng], namespaces);
|
|
37
|
+
return i18n.getDataByLanguage(lng);
|
|
38
|
+
}
|
|
39
|
+
i18n.use(ICU).use(
|
|
40
|
+
resourcesToBackend((lng, ns) => {
|
|
41
|
+
const loader = loaders[lng]?.[ns];
|
|
42
|
+
if (!loader) return Promise.reject(new Error(`no locale ${lng}/${ns}`));
|
|
43
|
+
return loader().then((m) => m.default || m);
|
|
44
|
+
})
|
|
45
|
+
).use(LanguageDetector).init({
|
|
46
|
+
ns: NS,
|
|
47
|
+
defaultNS: "common",
|
|
48
|
+
fallbackNS: "common",
|
|
49
|
+
partialBundledLanguages: true,
|
|
50
|
+
supportedLngs: SUPPORTED_LANGUAGES,
|
|
51
|
+
nonExplicitSupportedLngs: true,
|
|
52
|
+
// 'en-US' -> 'en', 'vi-VN' -> 'vi'
|
|
53
|
+
load: "languageOnly",
|
|
54
|
+
fallbackLng: resolveFallback,
|
|
55
|
+
// Namespaced keys `ns:section.leaf`. Split the namespace on ':'; the leaf
|
|
56
|
+
// (e.g. 'list.title') is a LITERAL flat key — dots are NOT path separators.
|
|
57
|
+
nsSeparator: ":",
|
|
58
|
+
keySeparator: false,
|
|
59
|
+
returnNull: false,
|
|
60
|
+
returnEmptyString: false,
|
|
61
|
+
interpolation: { escapeValue: false },
|
|
62
|
+
// React already escapes
|
|
63
|
+
detection: {
|
|
64
|
+
order: ["localStorage", "navigator"],
|
|
65
|
+
lookupLocalStorage: "lumi_lang",
|
|
66
|
+
caches: []
|
|
67
|
+
// language-store.js is the single writer
|
|
68
|
+
},
|
|
69
|
+
react: { useSuspense: false }
|
|
70
|
+
});
|
|
71
|
+
if (typeof document !== "undefined") {
|
|
72
|
+
const applyHtmlLang = (lng) => {
|
|
73
|
+
document.documentElement.setAttribute("lang", lng);
|
|
74
|
+
};
|
|
75
|
+
applyHtmlLang(i18n.resolvedLanguage || i18n.language);
|
|
76
|
+
i18n.on("languageChanged", applyHtmlLang);
|
|
77
|
+
}
|
|
78
|
+
var i18n_default = i18n;
|
|
79
|
+
export {
|
|
80
|
+
NS,
|
|
81
|
+
SUPPORTED_LANGUAGES,
|
|
82
|
+
i18n_default as default,
|
|
83
|
+
loadNamespacesForLanguage,
|
|
84
|
+
resolveFallback
|
|
85
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getUser, setUser } from "../../utils/auth.js";
|
|
2
|
+
import { updateMyLanguage } from "../../api/user.js";
|
|
3
|
+
import { SUPPORTED_LANGUAGES } from "./index.js";
|
|
4
|
+
const STORAGE_KEY = "lumi_lang";
|
|
5
|
+
const languageStore = {
|
|
6
|
+
load() {
|
|
7
|
+
let user = null;
|
|
8
|
+
try {
|
|
9
|
+
user = getUser();
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
if (user) {
|
|
13
|
+
return SUPPORTED_LANGUAGES.includes(user.language) ? user.language : "vi";
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return localStorage.getItem(STORAGE_KEY) || null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
// Resolve the language for a user who has just logged in.
|
|
22
|
+
//
|
|
23
|
+
// `user.language` is the SINGLE SOURCE OF TRUTH. Two things are deliberately
|
|
24
|
+
// NOT consulted here:
|
|
25
|
+
//
|
|
26
|
+
// - The localStorage cache. `lumi_lang` is a DEVICE-level value that
|
|
27
|
+
// outlives logout, so reading it made one account inherit the previous
|
|
28
|
+
// account's language, and nothing rewrote it afterwards, so a refresh
|
|
29
|
+
// brought it straight back.
|
|
30
|
+
// - navigator.language. An earlier version fell back to the browser locale,
|
|
31
|
+
// which is 'en-US' for a great many Vietnamese users and therefore served
|
|
32
|
+
// English to the majority audience — the opposite of the intent.
|
|
33
|
+
//
|
|
34
|
+
// The backend now defaults user.language to 'vi' and every existing user was
|
|
35
|
+
// backfilled (contact-center-be migration/280726-backfill-user-language-vi.js),
|
|
36
|
+
// so the field is effectively always present. The 'vi' below is
|
|
37
|
+
// defence-in-depth for a document that predates the backfill or was written
|
|
38
|
+
// by another service — NOT a locale guess.
|
|
39
|
+
resolveForUser(user) {
|
|
40
|
+
const profileLang = user?.language;
|
|
41
|
+
return SUPPORTED_LANGUAGES.includes(profileLang) ? profileLang : "vi";
|
|
42
|
+
},
|
|
43
|
+
// Cache-only write. Unlike save(), this does NOT merge onto the stored
|
|
44
|
+
// account and does NOT PATCH the DB — persisting a browser-derived default
|
|
45
|
+
// would invent a preference the user never expressed.
|
|
46
|
+
cache(lang) {
|
|
47
|
+
try {
|
|
48
|
+
localStorage.setItem(STORAGE_KEY, lang);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
save(lang) {
|
|
53
|
+
try {
|
|
54
|
+
localStorage.setItem(STORAGE_KEY, lang);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const user = getUser();
|
|
59
|
+
if (user) setUser({ ...user, language: lang });
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (getUser()?.token) {
|
|
64
|
+
return updateMyLanguage(lang).catch(() => {
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
return Promise.resolve();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export {
|
|
73
|
+
languageStore
|
|
74
|
+
};
|