Compare commits

..
Author SHA1 Message Date
Pranav Raj S 46b91ad840 Running version 2024-01-27 23:33:15 -08:00
Pranav Raj S ed8b58ad10 Remove foundation 2024-01-27 23:25:10 -08:00
Pranav Raj S 4928f28c64 Remove ionicons 2024-01-27 23:23:27 -08:00
Pranav Raj S 30539c27f2 Merge branch 'develop' into vite-ruby-migration 2024-01-27 23:21:44 -08:00
Pranav Raj SandGitHub 12916ceca6 fix: Capture delivery errors to avoid false positives (#8790)
The system did not detect the delivery errors earlier, resulting in some false positives. The user was not informed when an email failed to be delivered. While we do handle failure status in other channels, we were not able to capture the actual delivery status for the email channel.

This pull request makes the following changes:

- Updated the class EmailReplyWorker to use the deliver_now method instead of deliver_later. This change is made to raise any errors that may occur with the SMTP connection. The errors are then captured and sent to Sentry, and the email is marked as failed. Previously, we did not consider the case of retries in the email channel, so this feature is currently not functioning. So, I have disabled the retry option. We will address this in a follow-up ticket.
- Downgraded the net-smtp gem to version 0.3.4. This change is made to avoid an argument error when using XOAUTH2.

Fixes: https://linear.app/chatwoot/issue/CW-3032/argumenterror-wrong-authentication-type-xoauth2-argumenterror
2024-01-26 14:22:18 +04:00
Pranav Raj S 2e747e7fa6 Remove webpack 2024-01-25 12:43:39 -08:00
Pranav Raj S a1b6b0c06c Running dashboard, help center 2024-01-25 12:32:50 -08:00
Pranav Raj S 1fe3960285 Upgrade @vuelidate to 2.x 2024-01-25 12:15:21 -08:00
Pranav Raj S c33c844039 Initial Vite Commit 2024-01-25 11:56:22 -08:00
3c952e6a4a fix: change email conversation not found exception to log (#8785)
* fix: change email conversation not found exception to log

* chore: refactor reply_mailbox methods

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-25 22:36:02 +05:30
Sojan JoseandGitHub 59184122f7 fix: [Snyk] Security upgrade administrate from 0.19.0 to 0.20.1 (#8741)
-  Upgrade  administrate  gem to latest
- Update the `show` partial with changes from upstream
2024-01-25 17:33:21 +04:00
381423b1ae fix: Removed author section from public help center (#8767)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-25 17:17:16 +05:30
Pranav Raj SandGitHub b7c9f779ad fix: Avoid processing reactions, ephemeral, request_welcome or unsupported messages (#8780)
Currently, we do not support reactions, ephemeral messages, or the request_welcome event for the WhatsApp channel. However, if this is the first event we receive in Chatwoot (i.e., there is no previous conversation or contact in Chatwoot), it will create a contact and a conversation without any messages. This confuses our customer, as it may appear that Chatwoot has missed some messages. There are multiple cases where this might be the first event we receive in Chatwoot. One quick example is when the user has sent an outbound campaign from another tool and their customers reacted to the message.

Another event like this is request_welcome event. WhatsApp has a concept for welcome messages. You can send an outbound message even though the user has not send a message. You can receive notifications through a webhook whenever a WhatsApp user initiates a chat with you for the first time. (Read the Welcome message section: https://developers.facebook.com/docs/whatsapp/cloud-api/phone-numbers/conversational-components/ ). Although this can help the business send a pro-active message to the user, we don't have it scoped in our feature set. For now, I'm ignoring this event.

Fixes https://linear.app/chatwoot/issue/CW-3018/whatsapp-handle-request-welcome-case-properly
Fixes https://linear.app/chatwoot/issue/CW-3017/whatsapp-handle-reactions-properly
2024-01-25 11:40:18 +04:00
Muhsin KelothandGitHub 904d76420d fix: Add last_activity_at to notification push event data (#8784)
fix: Add last_activity_at to push event data
2024-01-25 12:05:00 +05:30
Muhsin KelothandGitHub fa907840c7 feat: Add middle_name and last_name to contact model (#8771)
feat: Add `middle_name` and `last_name`
2024-01-24 16:22:04 +05:30
3760f206e8 fix: mutex timeout and error handling (#8770)
Fixes the follow cases 
- The ensure block released the lock even on LockAcquisitionError
- Custom timeout was not allowed

This also refactored the with_lock method, now the key has to be constructed in the parent function itself

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-24 14:18:21 +04:00
Sojan JoseandGitHub a861257f73 chore: Fix flaky contacts spec (#8773)
- The ordering was not guaranteed; hence, the specs were failing randomly. Made changes to the expectations accordingly
2024-01-24 13:58:27 +04:00
Muhsin KelothandGitHub 143299f138 feat: Add contact_type attribute to contact model (#8768) 2024-01-24 12:26:47 +05:30
Vishnu NarayananandGitHub 232369cd5c feat: sla 1 - refactor sla_policies model and add applied_sla model (#8602)
* feat: add models

* chore: refactor sla column names

* chore: remove foreign keys

* chore: fix spec

* chore: refactor models
2024-01-23 23:48:02 +05:30
Surabhi SumanandGitHub 4b40c61201 feat: Support Regex validation for custom attributes (#7856)
This allows a user to add/update a custom regex and a cue while defining custom attributes(Only applicable for type- text).
While adding/editing custom attributes, the values are validated against the attribute definition regex, and if it is incorrect, a cue message or default error message is shown and restricts invalid values from being saved.

Fixes: #6866
2024-01-23 18:01:57 +04:00
834c219b9b feat(perf): update query to do a simpler search [CW-2997] (#8763)
Message search would frequently timeout. The reason was that the query would join the conversation too, the new query searches the message table directly

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-23 14:20:00 +04:00
Sivin VargheseandGitHub d0cd1c8887 fix: Help center articles not accessible after authoring agent is deleted (#8756) 2024-01-23 14:06:51 +05:30
Muhsin KelothandGitHub 682a2aea1c chore: Handle twillio Down::ClientError (#8757)
Fixes: https://linear.app/chatwoot/issue/CW-2992/downclienterror-400-bad-request-downclienterror
2024-01-22 15:33:26 +04:00
Shivam MishraandGitHub 1dc66db516 fix: SQL error when rules with missing attributes is triggered (#8673) 2024-01-22 16:09:34 +05:30
Sojan JoseandGitHub bc04d81a5a fix: Handle Net::IMAP::InvalidResponseError Exception bad response type "ESMTP" (#8755) 2024-01-22 15:32:44 +05:30
d2c5c2f9a3 chore: [Snyk] Security upgrade sidekiq from 7.2.0 to 7.2.1 (#8748)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-01-22 13:12:26 +04:00
Sojan JoseandGitHub 381fda270a chore: Fix typo in Inbox Management copy (#8750)
- Fixes the typo in Inbox Management copy -> vistors to visitors
2024-01-22 13:07:36 +04:00
b3c9d1f1a5 fix: clear timers and animation frame request before component unmounts (#8700)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-22 12:08:33 +05:30
Sivin VargheseandGitHub fd4376d062 fix: TypeError: Cannot read properties of undefined (reading 'emoji') (#8753) 2024-01-22 12:06:59 +05:30
Pranav Raj SandGitHub a8f053921b fix: Migrate notes when merging the contacts (#8749)
Fixes: https://linear.app/chatwoot/issue/CW-2987/migrate-notes-of-the-secondary-contact-to-primary-contact-when-merging
2024-01-20 10:32:18 +04:00
Sivin VargheseandGitHub 8e15ada164 fix: TypeError: Cannot read properties of undefined (reading 'emoji') (#8747) 2024-01-19 19:55:51 +05:30
Sojan 51093fd543 Merge branch 'release/3.5.2' into develop 2024-01-19 15:15:33 +04:00
Sojan 48e638cc56 Bump version to 3.5.2 2024-01-19 15:14:49 +04:00
Pavel KuzminandGitHub e6dfc159ed fix: change teamid (#8613) 2024-01-19 16:15:28 +05:30
Chatwoot BotandGitHub 900c2de89a chore: Update translations (#8717) 2024-01-19 14:32:27 +04:00
29106ebc4b fix: raise_lock_acquisition_error if the job cannot set the lock (#8744)
Consider a scenario where two jobs are concurrently accessing a job with a lock mechanism. If the second job accesses lock_manager.locked? before the first job called lock_manager.lock(lock_key), it would return a false positive. At this point, both jobs can be executed freely.

To address this issue, the following change ensures that only the current thread that has set the lock can execute. Otherwise, a LockAcquisitionError will be thrown.

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-01-19 14:31:41 +04:00
Pranav Raj SandGitHub a579684b34 fix: Update the message-id check to use messageId parsing from mail gem (#8743)
- Update the message ID parsing logic to use the mail gem
- Update the code to improve readability
2024-01-19 14:29:04 +04:00
c29a9ad062 feat: Updates branding logo and name in public portal footer (#8745)
- Use white-label settings for Chatwoot Help Center

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-19 13:37:24 +04:00
Muhsin KelothandGitHub 0ac015ce7a feat: Add a job to clear notifications that were created before 1 month (#8732) 2024-01-19 13:03:23 +05:30
19474e0074 chore: Improve active job error logs for deserialization error (#8742)
- Improve active job error logs for deserialization error 

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-18 19:27:18 +04:00
Sojan JoseandGitHub ce8190dacf fix: API error when using SuperAdmin token (#8739)
- Fixes the issue in release 3.5.0, which causes SuperAdmin tokens to throw error during API calls

Fixes: #8719
2024-01-18 18:49:32 +04:00
Shivam MishraandGitHub aacf326ca1 refactor: remove exception tracker (#8737) 2024-01-18 17:49:27 +05:30
Shivam MishraandGitHub e39c14460b fix: validate url for Dashboard Apps [CW-2979] (#8736) 2024-01-18 17:48:30 +05:30
Sojan JoseandGitHub 5f6e17f307 feat: Use Telegram HTML Parsemode (#8731)
- this ensures that the markdown formatted messages from the Chatwoot dashboard will render consistently in telegram UI for the supported types like bold, italics, links etc
2024-01-18 15:36:36 +04:00
Shivam MishraandGitHub 4bf23adcf5 feat: use short_summary for downloading reports [CW-2962] (#8733) 2024-01-18 16:06:13 +05:30
Pranav Raj SandGitHub 1f4d860d9d fix: Use channel.inbox instead of inbox (#8734) 2024-01-18 00:46:25 -08:00
fdbb3bf4b1 fix: Optimize email fetching logic to fix bandwidth exceeded issues (#8730)
The Inboxes::FetchImapEmailsJob is designed to fetch the entire email object and check if its message id is already in the database. However, this process is resource-intensive and time-consuming, as fetching the full email object takes a significant amount of time.

On average, fetching 100 emails can take approximately 3-4 minutes to complete depending on the IMAP server. Since we are not using server flags to identify which emails have already been fetched (to avoid compatibility issues with flags in different email services), we have to fetch all previously available emails. Currently we fetch all the messages that were created from yesterday. This becomes problematic with services like Gmail, which throttle requests based on bandwidth usage.

To address this issue, I have updated the logic as follows:

Fetch the sequence IDs of all the mails in the "Inbox" that were created from yesterday.
Use the FETCH command to fetch only the message-ids using BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)] with the sequence IDs we got in the previous step. This is a faster operation with lower bandwidth usage, as it only returns the sequence ID and message ID.
Check if the message IDs are already present in the database for the selected inbox.
If not present, fetch the entire email object using the FETCH command and create the message.
If the message ID is already present, ignore it and move on to the next message-id.
I have also addressed the issue of duplicate emails appearing in conversations when two fetch email jobs occur simultaneously. I have added a lock for the channel to ensure that the job gracefully exits without waiting for the current job to complete.

Fixes #7247
Fixes #6082
Fixes #8314

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-18 11:45:16 +04:00
Muhsin KelothandGitHub c899cc825d fix: Handle Contact import MalformedCSVError (#8706) 2024-01-18 13:05:58 +05:30
Muhsin KelothandGitHub eb972684b3 feat: Show notification last active time instead of created time (#8724) 2024-01-18 09:41:53 +05:30
Muhsin KelothandGitHub 896473f03e fix: Notification count is incorrect when the number of notifications exceeds the page size. (#8723)
- The notification count is incorrect when the number of notifications exceeds the page size.
2024-01-17 15:14:37 +04:00
Sojan 8c43edbd50 Merge branch 'release/3.5.1' into develop 2024-01-17 13:48:28 +04:00
Sojan 4c4eb4bd90 Bump version to 3.5.1 2024-01-17 13:47:57 +04:00
Vishnu NarayananandGitHub 88b8597e4d chore: log emails sent from chatwoot (#8721)
- Log details related to emails sent from Chatwoot to assist in debugging email delivery/bounces/complaints
2024-01-17 13:19:14 +04:00
Sojan JoseandGitHub 76fe2f9bb8 chore: Switch to Markdown instead of MarkdownV2 for telegram channel (#8720) 2024-01-17 14:28:13 +05:30
818424259f chore: Get all notification API improvments (#8549)
Co-authored-by: Sojan Jose <sojan@chatwoot.com>
2024-01-17 09:02:18 +05:30
Sivin VargheseandGitHub e67f8824d9 fix: Backdrop color for insert article modal in dark mode (#8711) 2024-01-16 18:36:43 +05:30
Sojan JoseandGitHub 8f1a1e0905 fix: Stop overwritting contact avatars unneccesarily (#8710)
While debugging a sentry error for "ActiveRecord::InvalidForeignKey ActiveStorage::Representations::RedirectController", it was noticed that we enqueue a Avatar::AvatarFromUrlJob for each setUser call, which is unnecessary. Hence making this call only if the contact doesn't have an existing avatar.

If one needs to have this avatar updated, they can go to the contacts tab and delete the current avatar, Chatwoot will pick up the new avatar in subsequent API call.
2024-01-16 16:38:46 +04:00
Mongkon KaenjanandGitHub 1b6360d9d3 chore: change OFFLINE text in th translations (#8642)
change OFFLINE text from เราไม่อยู่ to ออฟไลน์
2024-01-16 15:18:59 +04:00
8b7f10fe61 feat: log errors on automation filter (#8702)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-01-16 16:13:57 +05:30
4958b3e7b6 chore: Fix codespaces not booting up (#8264)
Fixed an issue where the codespace was not correctly booting up.
Fixes #7609

Co-authored-by: Liam Ashdown <liam.ashdown@debbiesvillas.co.uk>
Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-16 14:34:23 +04:00
e467d15f2c chore: Update translations (#8544)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-16 13:49:38 +04:00
d1e7e75638 feat: Adds internal support for markdown in Telegram outgoing messages (#8640)
This PR adds support for Markdown in the telegram API for send message. The dashboard uses commonmark syntax for markdown but telegram is using MarkdownV2.
More info - https://core.telegram.org/bots/api#markdownv2-style

Adds support for bold - *bold*, _italic text_, __underline__, ~strikethrough~

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-16 13:32:59 +04:00
Sojan 4aab63b7df Merge branch 'release/3.5.0' into develop 2024-01-16 11:15:29 +04:00
Sojan acf24649b0 Bump version to 3.5.0 2024-01-16 11:14:38 +04:00
Sojan JoseandGitHub 316cf35795 chore: Add display manifest to whitelabel settings (#8708)
- Adds display manifest to white label settings
- Improve the app config rendering with options for display name and description
2024-01-16 09:50:23 +04:00
Muhsin KelothandGitHub 52a5a59ddb fix: Handle error if the Twillio attachment download fails to download (#8705) 2024-01-15 19:13:44 +05:30
Sivin VargheseandGitHub 22db201a08 chore: Hide the variable list if its empty. (#8704) 2024-01-15 15:45:36 +05:30
ea7898d113 feat: fallback to DB localStorage for idb names (#8682)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-01-15 15:08:15 +05:30
Vishnu NarayananandGitHub e7e14f01e4 fix: undefined method 'zero?' for nil:NilClass (#8689) 2024-01-15 15:07:36 +05:30
Muhsin KelothandGitHub 7d6085cefd fix: Log twillio attachment error (#8703) 2024-01-15 12:46:58 +05:30
Shivam MishraandGitHub 10076c6a3e feat: set lock timeout to 1 second (#8661) 2024-01-12 08:28:07 +05:30
Vishnu NarayananandGitHub 1c44445088 chore: Update the error message to the correct one (#8646)
-  Update the error message in reply_mailbox to more appropriate one.
2024-01-11 17:38:38 -08:00
d305c5fd0f fix: Check the file attachment URL exists before downloading attachments in Telegram. (#8679)
In rare cases, the API call to Telegram for the file path fails. We were logging the error in sentry, switching to logs instead.

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-11 17:25:46 -08:00
Muhsin KelothandGitHub 1577288843 fix: Twilo attachment download fallback to the step where authorization headers are not passed. (#8681)
In the previous release, we enabled "HTTP Basic Authentication" to secure all attachments requiring HTTP authentication. This is particularly important for media files that may contain sensitive data, as recommended by Twilio. However, some users experienced issues because they did not enable this option despite our alerts prompting them to do so.  If the authenticated attachment download call fails, add another call to download the attachment without authentication.
2024-01-11 17:23:31 -08:00
Muhsin KelothandGitHub 63b3686746 chore: Add a check to verify if the user exists before broadcasting the notification_deleted event. (#8678)
When a user is deleted, all associated notifications are also deleted from the database. As a result, the user record becomes empty. handling this case in code to prevent sentry errors.
2024-01-11 16:54:03 -08:00
810f950e16 fix: Fixes error with editor when --- is inserted (#8669)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-01-11 07:56:18 -08:00
dd09d560d5 fix: logic error when setting new relic logging forwarding (#8687)
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
2024-01-11 21:05:04 +05:30
Sivin VargheseandGitHub c60c523851 fix: TypeError cannot read properties of undefined (reading 'queryselector') (#8671) 2024-01-11 20:42:24 +05:30
22c2235d90 fix: TypeError: Cannot read properties of null (reading 'assignee') (#8647)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-01-11 11:31:02 +05:30
Sojan JoseandGitHub 35c26367da chore: Unset Redis config after spec (#8685)
The sentinel configuration set in this specification seems to be affecting other specifications. So, let's ensure that the memoised config variable gets unset after execution of the spec.
2024-01-10 15:32:48 -08:00
Sojan JoseandGitHub aaf4fee9a6 chore: Fix sentry errors in email processing for bounce notifications (#8677)
This case occurs for bounce notification emails where the from address is From: "" (Mail Delivery System) . We will be discarding these emails for now.

Fixes: https://linear.app/chatwoot/issue/CW-2793/activerecordrecordinvalid-validation-failed-email-invalid-email
2024-01-10 14:30:23 -08:00
Sivin VargheseandGitHub 3c21f62485 fix: Error ResizeObserver loop completed with undelivered notifications. (#8680) 2024-01-10 11:44:13 -08:00
50b2ca014e feat: UI for unsupported message (#8660)
Display an unsupported message in UI when handling unsupported messages from channels like facebook, Instagram etc.

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-09 21:53:58 -08:00
Vishnu NarayananandGitHub d731c972ad fix: skip auditlogs for whatsapp template sync (#8579)
Skips audit logs for whatsapp_template sync
Fixes: https://linear.app/chatwoot/issue/CW-2641/skip-whatsapp-template-updates-from-audit-logs
2024-01-09 21:51:48 -08:00
Sojan JoseandGitHub 5845881b08 chore: Handle stripe events without plan data (#8668)
`plan` isn't a guaranteed object in stripe `customer.subscription.updated` events.  It can be null for cases like `send_invoice` for `past_due` event as seen in the payload shown in sentry error.

fixes: https://linear.app/chatwoot/issue/CW-2925/nomethoderror-undefined-method-[]-for-nilnilclass-nomethoderror
2024-01-09 14:48:07 -08:00
Muhsin KelothandGitHub 64138ef220 chore: Rescue Slack::Web::Api::Errors::NotInChannel error (#8670)
The primary cause of this issue is when Chatwoot sends a message to a channel that has either been deleted or is unauthorized. So, we will prompt reauthorization when this error occurs.

Fixes https://linear.app/chatwoot/issue/CW-2930/slackwebapierrorsnotinchannel-not-in-channel
2024-01-09 14:30:17 -08:00
Sojan JoseandGitHub 046ce68a45 chore: Improve Openai json rendering (#8666)
We have been observing JSON parsing errors for responses from GPT. Switching to the gpt-4-1106-preview model along with using response_format has significantly improved the responses from OpenAI, hence making the switch in code.

ref: https://openai.com/blog/new-models-and-developer-products-announced-at-devday
fixes: #CW-2931
2024-01-08 17:02:25 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e34ab5957f chore(deps): bump puma from 6.3.1 to 6.4.2 (#8663)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 16:51:30 -08:00
Pranav Raj SandGitHub 75a5492840 fix: Remove the usage of DragWrapper to fix the Dyte integration (#8655) 2024-01-05 15:28:20 -08:00
56fbbe92b4 feat: trigger handoff when agent bot is the actor (#8639)
- This PR adds a feature to auto-trigger handoff events when an Agent bot toggles a conversation status from Pending to Open

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-05 15:26:52 -08:00
Sojan JoseandGitHub dc4e13b300 chore: Fix for empty update case for messages (#8641)
We observed an issue in production where the external webhook for an API inbox was failing. This, in turn, calls message update to update message status to failed. This causes a loop because rails trigger after_update callbacks even for empty commits.

Ref: rails/rails#44500
2024-01-05 13:10:26 -08:00
Pranav Raj SandGitHub 2c7f93978e fix: Update broken specs (#8651)
- Use fakeTimer for time.spec.js
- Use default sort as last_activity_at_desc
- Update specs for getAllConversations getter
2024-01-05 13:09:09 -08:00
Shivam MishraandGitHub 6e30064421 feat: handle unsupported media on the backend (#8650)
This PR logs additional information in content_attributes of a message in case it is unsupported. This info can be used by the client to render a fresh UI
2024-01-05 13:05:00 -08:00
Muhsin KelothandGitHub 68062216e4 feat: Add a job to reopen snoozed notifications (#8545) 2024-01-05 15:13:22 +05:30
Vishnu NarayananandGitHub 8a8f325f64 fix: messageTimestamp test (#8644) 2024-01-05 12:36:26 +05:30
Kevin LinandGitHub cf90b898f4 feat: enable new relic log decorations (#8616)
This enables local decorating of logs when using a new relic. It can be used when you use a log forwarding agent to do additional enrichment on APM logs before forwarding to the new relic.
2024-01-04 15:34:55 -08:00
Sojan JoseandGitHub 1f4cb0e1ca chore: add config for Darkmode logo for whitelabeling (#8597)
-  Add config for dark mode logo
2024-01-04 15:30:55 -08:00
c6ecf80dff chore(synk): Upgrade administrate to fix 2 vulnerabilities (#8625)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2024-01-04 13:38:03 -08:00
c29b77c214 chore(snyk): Security upgrade factory_bot_rails from 6.4.2 to 6.4.3 (#8628)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-01-04 10:47:48 -08:00
Pranav Raj SandGitHub c301845ade chore: 2023 -> 2024 (#8635) 2024-01-03 12:49:36 -08:00
Sivin VargheseandGitHub 78fab7897d feat: Use typing status from utils (#8589) 2023-12-22 13:47:41 +05:30
f48e014694 feat: Updates the design of the network connection warning snack bar (#8509)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-12-21 14:16:21 -08:00
Nithin David ThomasandGitHub 206433db32 feat: Adds dropdown to switch locales from articles list page (#8402) 2023-12-21 13:16:59 -08:00
1ddb73ea97 chore(security): Security upgrade omniauth from 2.1.1 to 2.1.2 (#8591)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-12-20 13:38:34 -08:00
Muhsin KelothandGitHub b5071e61d7 feat: Add last_activity_at field to Notification model (#8585) 2023-12-20 13:02:37 +05:30
Muhsin KelothandGitHub a80eff9aa3 feat: Enable custom attributes in the suggestion for variables. (#8520) 2023-12-20 12:20:09 +05:30
Sojan a59fb90785 Merge branch 'release/3.4.0' into develop 2023-12-18 21:23:02 -08:00
863 changed files with 14975 additions and 23076 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ jobs:
name: yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache
# Store yarn cache
- save_cache:
key: chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }}
paths:
+1 -1
View File
@@ -2,7 +2,7 @@
# It has the dependencies already installed so that codespace will boot up fast
FROM ghcr.io/chatwoot/chatwoot_codespace:latest
# Do the set up required for chatwoot app
# Do the set up required for chatwoot app
WORKDIR /workspace
COPY . /workspace
RUN yarn && gem install bundler && bundle install
+14 -11
View File
@@ -1,10 +1,14 @@
ARG VARIANT=ubuntu-20.04
FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT}
ARG VARIANT
FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
ARG NODE_VERSION
ARG RUBY_VERSION
ARG USER_UID
ARG USER_GID
# Update args in docker-compose.yaml to set the UID/GID of the "vscode" user.
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
groupmod --gid $USER_GID vscode \
&& usermod --uid $USER_UID --gid $USER_GID vscode \
@@ -27,10 +31,10 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
tmux \
zsh \
git-flow \
npm
npm \
libyaml-dev
# Install rbenv and ruby
ARG RUBY_VERSION="3.2.2"
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv \
&& echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc \
&& echo 'eval "$(rbenv init -)"' >> ~/.bashrc
@@ -46,7 +50,7 @@ RUN rbenv install $RUBY_VERSION && \
RUN curl -L https://github.com/DarthSim/overmind/releases/download/v2.1.0/overmind-v2.1.0-linux-amd64.gz > overmind.gz \
&& gunzip overmind.gz \
&& sudo mv overmind /usr/local/bin \
&& chmod +x /usr/local/bin/overmind
&& chmod +x /usr/local/bin/overmind
# Install gh
@@ -56,7 +60,7 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | s
&& sudo apt install gh
# Do the set up required for chatwoot app
# Do the set up required for chatwoot app
WORKDIR /workspace
COPY . /workspace
@@ -65,8 +69,7 @@ COPY Gemfile Gemfile.lock ./
RUN gem install bundler && bundle install
# set up node js
RUN npm install npm@latest -g && \
npm install n -g && \
n latest
RUN npm install n -g && \
n $NODE_VERSION
RUN npm install --global yarn
RUN yarn
+23 -13
View File
@@ -1,30 +1,41 @@
# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/python-3-postgres/.devcontainer/docker-compose.yml
# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/ruby-rails/.devcontainer/devcontainer.json
#
#
version: '3'
services:
app:
base:
build:
context: ..
dockerfile: .devcontainer/Dockerfile.base
args:
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.2.2"
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: "1000"
USER_GID: "1000"
image: base:latest
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
# Update 'VARIANT' to pick a Ruby version: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby
VARIANT: 3
# [Choice] Install Node.js
INSTALL_NODE: "true"
NODE_VERSION: "lts/*"
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.2.2"
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: 1000
USER_GID: 1000
USER_UID: "1000"
USER_GID: "1000"
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
@@ -49,8 +60,7 @@ services:
restart: unless-stopped
image: mailhog/mailhog
network_mode: service:db
volumes:
postgres-data:
redis-data:
+4 -4
View File
@@ -3,10 +3,10 @@ sed -i -e '/REDIS_URL/ s/=.*/=redis:\/\/localhost:6379/' .env
sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env
sed -i -e '/SMTP_ADDRESS/ s/=.*/=localhost/' .env
sed -i -e "/FRONTEND_URL/ s/=.*/=https:\/\/$CODESPACE_NAME-3000.githubpreview.dev/" .env
sed -i -e "/WEBPACKER_DEV_SERVER_PUBLIC/ s/=.*/=https:\/\/$CODESPACE_NAME-3035.githubpreview.dev/" .env
# uncomment the webpacker env variable
sed -i -e '/WEBPACKER_DEV_SERVER_PUBLIC/s/^# //' .env
# fix the error with webpacker
sed -i -e "/DEV_SERVER_PUBLIC/ s/=.*/=https:\/\/$CODESPACE_NAME-3035.githubpreview.dev/" .env
# uncomment the dev server env variable
sed -i -e '/DEV_SERVER_PUBLIC/s/^# //' .env
# fix the error with dev server
echo 'export NODE_OPTIONS=--openssl-legacy-provider' >> ~/.zshrc
# codespaces make the ports public
+1 -1
View File
@@ -223,7 +223,7 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
# if you want to use letter_opener for local emails
# LETTER_OPENER=true
# meant to be used in github codespaces
# WEBPACKER_DEV_SERVER_PUBLIC=
# DEV_SERVER_PUBLIC=
# If you want to use official mobile app,
# the notifications would be relayed via a Chatwoot server
+2 -10
View File
@@ -7,11 +7,10 @@ module.exports = {
'plugin:cypress/recommended',
],
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 2020,
ecmaVersion: 2022,
sourceType: 'module',
},
plugins: ['html', 'prettier', 'babel'],
plugins: ['html', 'prettier'],
rules: {
'prettier/prettier': ['error'],
camelcase: 'off',
@@ -56,13 +55,6 @@ module.exports = {
'import/extensions': ['off'],
'no-console': 'error',
},
settings: {
'import/resolver': {
webpack: {
config: 'config/webpack/resolve.js',
},
},
},
env: {
browser: true,
jest: true,
@@ -19,5 +19,6 @@ jobs:
- name: Build the Codespace Base Image
run: |
docker build . -t ghcr.io/chatwoot/chatwoot_codespace:latest -f .devcontainer/Dockerfile.base
docker-compose -f .devcontainer/docker-compose.yml build base
docker tag base:latest ghcr.io/chatwoot/chatwoot_codespace:latest
docker push ghcr.io/chatwoot/chatwoot_codespace:latest
+9 -1
View File
@@ -75,4 +75,12 @@ yalc.lock
yarn-debug.log*
.yarn-integrity
/storybook-static
/storybook-static
# Vite Ruby
/public/vite*
node_modules
# Vite uses dotenv and suggests to ignore local-only env files. See
# https://vitejs.dev/guide/env-and-mode.html#env-files
*.local
+1 -3
View File
@@ -2,7 +2,6 @@ import { addDecorator } from '@storybook/vue';
import Vue from 'vue';
import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import Vuelidate from 'vuelidate';
import Multiselect from 'vue-multiselect';
import VueDOMPurifyHTML from 'vue-dompurify-html';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
@@ -14,7 +13,6 @@ import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer';
import '../app/javascript/dashboard/assets/scss/storybook.scss';
Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(WootUiKit);
Vue.use(Vuex);
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
@@ -32,7 +30,7 @@ addDecorator(() => ({
template: '<story/>',
i18n: i18nConfig,
store,
beforeCreate: function() {
beforeCreate: function () {
this.$root._i18n = this.$i18n;
},
}));
+9 -7
View File
@@ -39,6 +39,8 @@ gem 'rack-attack', '>= 6.7.0'
gem 'down'
# authentication type to fetch and send mail over oauth2.0
gem 'gmail_xoauth'
# Lock net-smtp to 0.3.4 to avoid issues with gmail_xoauth2
gem 'net-smtp', '~> 0.3.4'
# Prevent CSV injection
gem 'csv-safe'
@@ -62,7 +64,7 @@ gem 'activerecord-import'
gem 'dotenv-rails'
gem 'foreman'
gem 'puma'
gem 'webpacker'
gem 'vite_rails'
# metrics on heroku
gem 'barnes'
@@ -74,9 +76,9 @@ gem 'devise_token_auth'
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.19.0'
gem 'administrate-field-active_storage', '>= 1.0.0'
gem 'administrate-field-belongs_to_search'
gem 'administrate', '>= 0.20.1'
gem 'administrate-field-active_storage', '>= 1.0.1'
gem 'administrate-field-belongs_to_search', '>= 0.9.0'
##--- gems for pubsub service ---##
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
@@ -114,7 +116,7 @@ gem 'sentry-ruby', require: false
gem 'sentry-sidekiq', '>= 5.14.0', require: false
##-- background job processing --##
gem 'sidekiq', '>= 7.1.3'
gem 'sidekiq', '>= 7.2.1'
# We want cron jobs
gem 'sidekiq-cron', '>= 1.12.0'
@@ -162,7 +164,7 @@ gem 'omniauth-oauth2'
gem 'audited', '~> 5.4', '>= 5.4.1'
# need for google auth
gem 'omniauth'
gem 'omniauth', '>= 2.1.2'
gem 'omniauth-google-oauth2'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
@@ -224,7 +226,7 @@ group :development, :test do
gem 'byebug', platform: :mri
gem 'climate_control'
gem 'debug', '~> 1.8'
gem 'factory_bot_rails', '>= 6.4.2'
gem 'factory_bot_rails', '>= 6.4.3'
gem 'listen'
gem 'mock_redis'
gem 'pry-rails'
+44 -41
View File
@@ -105,21 +105,21 @@ GEM
activerecord (>= 6.0, < 7.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
administrate (0.19.0)
actionpack (>= 5.0)
actionview (>= 5.0)
activerecord (>= 5.0)
jquery-rails (>= 4.0)
kaminari (>= 1.0)
administrate (0.20.1)
actionpack (>= 6.0, < 8.0)
actionview (>= 6.0, < 8.0)
activerecord (>= 6.0, < 8.0)
jquery-rails (~> 4.6.0)
kaminari (~> 1.2.2)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
administrate-field-active_storage (1.0.0)
administrate-field-active_storage (1.0.1)
administrate (>= 0.2.2)
rails (>= 7.0)
administrate-field-belongs_to_search (0.8.0)
administrate-field-belongs_to_search (0.9.0)
administrate (>= 0.3, < 1.0)
jbuilder (~> 2)
rails (>= 4.2, < 7.1)
rails (>= 4.2, < 7.2)
selectize-rails (~> 0.6)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
@@ -169,7 +169,7 @@ GEM
climate_control (1.2.0)
coderay (1.1.3)
commonmarker (0.23.10)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crack (0.4.5)
rexml
@@ -216,6 +216,7 @@ GEM
railties (>= 3.2)
down (5.4.0)
addressable (~> 2.8)
dry-cli (1.0.0)
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
elastic-apm (4.6.2)
@@ -230,9 +231,9 @@ GEM
facebook-messenger (2.0.1)
httparty (~> 0.13, >= 0.13.7)
rack (>= 1.4.5)
factory_bot (6.4.2)
factory_bot (6.4.5)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.2)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faker (3.2.0)
@@ -461,7 +462,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.20.0)
minitest (5.21.2)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
@@ -472,14 +473,14 @@ GEM
activerecord (>= 5.2)
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.5)
net-imap (0.4.9)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.4.0)
net-smtp (0.3.4)
net-protocol
netrc (0.11.0)
newrelic-sidekiq-metrics (1.6.2)
@@ -487,15 +488,15 @@ GEM
sidekiq
newrelic_rpm (9.6.0)
base64
nio4r (2.6.0)
nokogiri (1.15.5)
nio4r (2.7.0)
nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.15.5-arm64-darwin)
nokogiri (1.16.0-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.5-x86_64-darwin)
nokogiri (1.16.0-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.15.5-x86_64-linux)
nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
@@ -511,7 +512,7 @@ GEM
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
omniauth (2.1.1)
omniauth (2.1.2)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
@@ -553,7 +554,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
puma (6.3.1)
puma (6.4.2)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
@@ -568,9 +569,9 @@ GEM
rack (>= 2.0.0)
rack-mini-profiler (3.2.0)
rack (>= 1.2.0)
rack-protection (3.0.6)
rack
rack-proxy (0.7.6)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rack-proxy (0.7.7)
rack
rack-test (2.1.0)
rack (>= 1.3)
@@ -610,7 +611,7 @@ GEM
ffi (~> 1.0)
redis (5.0.6)
redis-client (>= 0.9.0)
redis-client (0.19.0)
redis-client (0.19.1)
connection_pool
redis-namespace (1.10.0)
redis (>= 4)
@@ -708,7 +709,6 @@ GEM
activerecord (>= 4)
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.14.0)
railties (>= 5.0)
sentry-ruby (~> 5.14.0)
@@ -720,11 +720,11 @@ GEM
sexp_processor (4.17.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.2.0)
sidekiq (7.2.1)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
redis-client (>= 0.14.0)
redis-client (>= 0.19.0)
sidekiq-cron (1.12.0)
fugit (~> 1.8)
globalid (>= 1.0.1)
@@ -795,6 +795,13 @@ GEM
activemodel (>= 3.2)
mail (~> 2.5)
version_gem (1.1.2)
vite_rails (3.0.17)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0, >= 3.2.2)
vite_ruby (3.5.0)
dry-cli (>= 0.7, < 2)
rack-proxy (~> 0.6, >= 0.6.1)
zeitwerk (~> 2.2)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -810,11 +817,6 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.4.4)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.8.1)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
@@ -840,9 +842,9 @@ DEPENDENCIES
active_record_query_trace
activerecord-import
acts-as-taggable-on
administrate (>= 0.19.0)
administrate-field-active_storage (>= 1.0.0)
administrate-field-belongs_to_search
administrate (>= 0.20.1)
administrate-field-active_storage (>= 1.0.1)
administrate-field-belongs_to_search (>= 0.9.0)
annotate
attr_extras
audited (~> 5.4, >= 5.4.1)
@@ -870,7 +872,7 @@ DEPENDENCIES
elastic-apm
email_reply_trimmer
facebook-messenger
factory_bot_rails (>= 6.4.2)
factory_bot_rails (>= 6.4.3)
faker
fcm
flag_shih_tzu
@@ -903,9 +905,10 @@ DEPENDENCIES
meta_request
mock_redis
neighbor
net-smtp (~> 0.3.4)
newrelic-sidekiq-metrics (>= 1.6.2)
newrelic_rpm
omniauth
omniauth (>= 2.1.2)
omniauth-google-oauth2
omniauth-oauth2
omniauth-rails_csrf_protection (~> 1.0)
@@ -939,7 +942,7 @@ DEPENDENCIES
sentry-ruby
sentry-sidekiq (>= 5.14.0)
shoulda-matchers
sidekiq (>= 7.1.3)
sidekiq (>= 7.2.1)
sidekiq-cron (>= 1.12.0)
simplecov (= 0.17.1)
slack-ruby-client (~> 2.2.0)
@@ -956,10 +959,10 @@ DEPENDENCIES
tzinfo-data
uglifier
valid_email2
vite_rails
web-console (>= 4.2.1)
web-push
webmock
webpacker
wisper (= 2.0.0)
working_hours
+1 -1
View File
@@ -1,4 +1,4 @@
backend: bin/rails s -p 3000
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
frontend: bin/vite dev
+1 -1
View File
@@ -1,3 +1,3 @@
backend: RAILS_ENV=test bin/rails s -p 5050
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
frontend: bin/vite
worker: RAILS_ENV=test dotenv bundle exec sidekiq -C config/sidekiq.yml
+1 -1
View File
@@ -118,4 +118,4 @@ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contri
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
*Chatwoot* &copy; 2017-2023, Chatwoot Inc - Released under the MIT License.
*Chatwoot* &copy; 2017-2024, Chatwoot Inc - Released under the MIT License.
+1 -1
View File
@@ -104,7 +104,7 @@ class ContactIdentifyAction
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
@contact.discard_invalid_attrs if discard_invalid_attrs
@contact.save!
Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present? && !@contact.avatar.attached?
end
def merge_contact(base_contact, merge_contact)
+5
View File
@@ -12,6 +12,7 @@ class ContactMergeAction
merge_conversations
merge_messages
merge_contact_inboxes
merge_contact_notes
merge_and_remove_mergee_contact
end
@base_contact
@@ -33,6 +34,10 @@ class ContactMergeAction
Conversation.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
end
def merge_contact_notes
Note.where(contact_id: @mergee_contact.id, account_id: @mergee_contact.account_id).update(contact_id: @base_contact.id)
end
def merge_messages
Message.where(sender: @mergee_contact).update(sender: @base_contact)
end
@@ -86,8 +86,5 @@ $swift-ease-out-duration: .4s !default;
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions
$transition-ease-in: all 0.250s ease-in;
@@ -48,6 +48,10 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
@outgoing_echo ? recipient_id : sender_id
end
def message_is_unsupported?
message[:is_unsupported].present? && @messaging[:message][:is_unsupported] == true
end
def sender_id
@messaging[:sender][:id]
end
@@ -118,7 +122,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
end
def message_params
{
params = {
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
message_type: message_type,
@@ -129,6 +133,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
in_reply_to_external_id: message_reply_attributes
}
}
params[:content_attributes][:is_unsupported] = true if message_is_unsupported?
params
end
def already_sent_from_chatwoot?
+4 -3
View File
@@ -2,8 +2,6 @@ class NotificationBuilder
pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!, :secondary_actor]
def perform
return unless user_subscribed_to_notification?
build_notification
end
@@ -16,7 +14,7 @@ class NotificationBuilder
def user_subscribed_to_notification?
notification_setting = user.notification_settings.find_by(account_id: account.id)
# added for the case where an assignee might be removed from the account but remains in conversation
return if notification_setting.blank?
return false if notification_setting.blank?
return true if notification_setting.public_send("email_#{notification_type}?")
return true if notification_setting.public_send("push_#{notification_type}?")
@@ -25,6 +23,9 @@ class NotificationBuilder
end
def build_notification
# Create conversation_creation notification only if user is subscribed to it
return if notification_type == 'conversation_creation' && !user_subscribed_to_notification?
user.notifications.create!(
notification_type: notification_type,
account: account,
+8
View File
@@ -46,6 +46,14 @@ class V2::ReportBuilder
}
end
def short_summary
{
conversations_count: conversations.count,
avg_first_response_time: avg_first_response_time_summary,
avg_resolution_time: avg_resolution_time_summary
}
end
def conversation_metrics
if params[:type].equal?(:account)
live_conversations
@@ -60,13 +60,26 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def toggle_status
if params[:status].present?
# FIXME: move this logic into a service object
if pending_to_open_by_bot?
@conversation.bot_handoff!
elsif params[:status].present?
set_conversation_status
@status = @conversation.save!
else
@status = @conversation.toggle_status
end
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
assign_conversation if should_assign_conversation?
end
def pending_to_open_by_bot?
return false unless Current.user.is_a?(AgentBot)
@conversation.status == 'pending' && params[:status] == 'open'
end
def should_assign_conversation?
@conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
end
def toggle_priority
@@ -39,6 +39,8 @@ class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Account
:attribute_display_type,
:attribute_key,
:attribute_model,
:regex_pattern,
:regex_cue,
attribute_values: []
)
end
@@ -7,9 +7,9 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
before_action :set_current_page, only: [:index]
def index
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
@count = notifications.count
@notifications = notifications.page(@current_page).per(RESULTS_PER_PAGE)
@unread_count = notification_finder.unread_count
@notifications = notification_finder.perform
@count = notification_finder.count
end
def read_all
@@ -35,7 +35,7 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
end
def unread_count
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
@unread_count = notification_finder.unread_count
render json: @unread_count
end
@@ -61,7 +61,7 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
@current_page = params[:page] || 1
end
def notifications
@notifications ||= current_user.notifications.where(account_id: current_account.id)
def notification_finder
@notification_finder ||= NotificationFinder.new(Current.user, Current.account, params)
end
end
@@ -14,7 +14,14 @@ module AccessTokenAuthHelper
render_unauthorized('Invalid Access Token') && return if @access_token.blank?
@resource = @access_token.owner
Current.user = @resource if current_user.is_a?(User)
Current.user = @resource if allowed_current_user_type?(@resource)
end
def allowed_current_user_type?(resource)
return true if resource.is_a?(User)
return true if resource.is_a?(AgentBot)
false
end
def validate_bot_access_token!
@@ -1,6 +1,7 @@
class Public::Api::V1::Portals::BaseController < PublicController
before_action :show_plain_layout
before_action :set_color_scheme
before_action :set_global_config
around_action :set_locale
after_action :allow_iframe_requests
@@ -60,4 +61,8 @@ class Public::Api::V1::Portals::BaseController < PublicController
portal
render 'public/api/v1/portals/error/404', status: :not_found
end
def set_global_config
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'BRAND_URL')
end
end
@@ -9,6 +9,9 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
.map { |name, serialized_value| [name, serialized_value['value']] }
.to_h
# rubocop:enable Style/HashTransformValues
@installation_configs = ConfigLoader.new.general_configs.each_with_object({}) do |config_hash, result|
result[config_hash['name']] = config_hash.except('name')
end
end
def create
+47
View File
@@ -0,0 +1,47 @@
class NotificationFinder
attr_reader :current_user, :current_account, :params
RESULTS_PER_PAGE = 15
def initialize(current_user, current_account, params = {})
@current_user = current_user
@current_account = current_account
@params = params
set_up
end
def perform
notifications
end
def unread_count
@notifications.where(read_at: nil).count
end
def count
@notifications.count
end
private
def set_up
find_all_notifications
filter_by_status
end
def find_all_notifications
@notifications = current_user.notifications.where(account_id: @current_account.id)
end
def filter_by_status
@notifications = @notifications.where('snoozed_until > ?', DateTime.now.utc) if params[:status] == 'snoozed'
end
def current_page
params[:page] || 1
end
def notifications
@notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: :desc)
end
end
+1 -1
View File
@@ -53,7 +53,7 @@ module Api::V1::InboxesHelper
rescue StandardError => e
raise StandardError, e.message
ensure
ChatwootExceptionTracker.new(e).capture_exception if e.present?
Rails.logger.error "[Api::V1::InboxesHelper] check_imap_connection failed with #{e.message}" if e.present?
end
def check_smtp_connection(channel_data, smtp)
@@ -37,7 +37,7 @@ module Api::V2::Accounts::ReportsHelper
business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours])
}
)
).summary
).short_summary
end
private
@@ -1,4 +1,4 @@
@import '~vue2-datepicker/scss/index';
@import 'vue2-datepicker/scss/index';
.date-picker {
&.no-margin {
@@ -1,4 +1,4 @@
@import '~dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/variables';
.formulate-input {
.formulate-input-errors {
@@ -1,623 +0,0 @@
// Foundation for Sites Settings
// -----------------------------
//
// Table of Contents:
//
// 1. Global
// 2. Breakpoints
// 3. The Grid
// 4. Base Typography
// 5. Typography Helpers
// 6. Abide
// 7. Accordion
// 8. Accordion Menu
// 9. Badge
// 10. Breadcrumbs
// 11. Button
// 12. Button Group
// 13. Callout
// 14. Card
// 15. Close Button
// 16. Drilldown
// 17. Dropdown
// 18. Dropdown Menu
// 19. Forms
// 20. Label
// 21. Media Object
// 22. Menu
// 23. Meter
// 24. Off-canvas
// 25. Orbit
// 26. Pagination
// 27. Progress Bar
// 28. Responsive Embed
// 29. Reveal
// 30. Slider
// 31. Switch
// 32. Table
// 33. Tabs
// 34. Thumbnail
// 35. Title Bar
// 36. Tooltip
// 37. Top Bar
@import '~foundation-sites/scss/util/util';
// 1. Global
// ---------
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
secondary: #5d7592,
success: #44ce4b,
warning: #ffc532,
alert: #ff382d);
$light-gray: #c0ccda;
$medium-gray: #8492a6;
$dark-gray: $color-gray;
$black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'PlusJakarta',
-apple-system,
system-ui,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Tahoma,
Arial,
sans-serif;
$body-antialiased: true;
$global-margin: $space-small;
$global-padding: $space-small;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;
$global-text-direction: ltr;
$global-flexbox: false;
$print-transparent-backgrounds: true;
@include add-foundation-colors;
// 2. Breakpoints
// --------------
$breakpoints: (small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1400px,
xxxlarge: 1600px,
);
$print-breakpoint: large;
$breakpoint-classes: (small medium large);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (small: $zero,
medium: $zero);
$grid-column-align-edge: true;
$block-grid-max: 8;
// 4. Base Typography
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $font-weight-medium;
$header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.3125rem;
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
"h4": ("font-size": 18),
"h5": ("font-size": 17),
"h6": ("font-size": 16)),
medium: ("h1": ("font-size": 48),
"h2": ("font-size": 40),
"h3": ("font-size": 31),
"h4": ("font-size": 25),
"h5": ("font-size": 20),
"h6": ("font-size": 16)));
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.65;
$paragraph-margin-bottom: var(--space-small);
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
$code-font-family: $font-family-monospace;
$code-font-weight: $global-weight-normal;
$code-background: $light-gray;
$code-border: 1px solid $medium-gray;
$code-padding: rem-calc(2 5 1);
$anchor-color: $primary-color;
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
$anchor-text-decoration: none;
$anchor-text-decoration-hover: none;
$hr-width: $global-width;
$hr-border: 1px solid $medium-gray;
$hr-margin: rem-calc(20) auto;
$list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 0.78125rem;
$list-nested-side-margin: 0.78125rem;
$defnlist-margin-bottom: 0.6875rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.1875rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$cite-pseudo-content: '\2014 \0020';
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
$keystroke-padding: rem-calc(2 4 0);
$keystroke-radius: $global-radius;
$abbr-underline: 1px dotted $black;
// 5. Typography Helpers
// ---------------------
$lead-font-size: $global-font-size * 1.25;
$lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.125rem;
$subheader-margin-bottom: 0.3125rem;
$stat-font-size: 1.5625rem;
// 6. Abide
// --------
$abide-inputs: true;
$abide-labels: true;
$input-background-invalid: get-color(alert);
$form-label-color-invalid: get-color(alert);
$input-error-color: get-color(alert);
$input-error-font-size: rem-calc(12);
$input-error-font-weight: $global-weight-bold;
// 7. Accordion
// ------------
$accordion-background: $white;
$accordion-plusminus: true;
$accordion-title-font-size: rem-calc(12);
$accordion-item-color: $primary-color;
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 0.78125rem 0.625rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: $body-font-color;
$accordion-content-padding: 0.625rem;
// 8. Accordion Menu
// -----------------
$accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color;
$accordionmenu-arrow-size: 6px;
// 9. Badge
// --------
$badge-background: $primary-color;
$badge-color: $white;
$badge-color-alt: $black;
$badge-palette: $foundation-palette;
$badge-padding: var(--space-smaller);
$badge-minwidth: 2.1em;
$badge-font-size: var(--font-size-nano);
// 10. Breadcrumbs
// ---------------
$breadcrumbs-margin: 0 0 $global-margin 0;
$breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.46875rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
// 11. Button
// ----------
$button-padding: var(--space-smaller) 1em;
$button-margin: 0 0 $global-margin 0;
$button-fill: solid;
$button-background: $primary-color;
$button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $white;
$button-radius: var(--border-radius-normal);
$button-sizes: (tiny: var(--font-size-micro),
small: var(--font-size-mini),
default: var(--font-size-small),
large: var(--font-size-medium));
$button-palette: $foundation-palette;
$button-opacity-disabled: 0.4;
$button-background-hover-lightness: -20%;
$button-hollow-hover-lightness: -50%;
$button-transition: background-color 0.25s ease-out,
color 0.25s ease-out;
// 12. Button Group
// ----------------
$buttongroup-margin: 0;
$buttongroup-spacing: 0;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
$buttongroup-radius-on-each: false;
// 13. Callout
// -----------
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 0.625rem 0;
$callout-padding: 0.625rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
$callout-link-tint: 30%;
// 14. Card
// --------
$card-background: $white;
$card-font-color: $body-font-color;
$card-divider-background: $light-gray;
$card-border: 1px solid var(--color-border);
$card-shadow: var(--shadow-small);
$card-border-radius: var(--border-radius-normal);
$card-padding: var(--space-small);
$card-margin: $global-margin;
// 15. Close Button
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: (small: 0.66rem,
medium: 1rem);
$closebutton-offset-vertical: (small: 0.33em,
medium: 0.5rem);
$closebutton-size: (small: 1.5em,
medium: 2em);
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
// 16. Drilldown
// -------------
$drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true;
$drilldown-arrow-color: $primary-color;
$drilldown-arrow-size: 6px;
$drilldown-background: $white;
// 17. Dropdown
// ------------
$dropdown-padding: 0.625rem;
$dropdown-background: $body-background;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (tiny: 100px,
small: 200px,
large: 400px);
// 18. Dropdown Menu
// -----------------
$dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-arrow-size: 6px;
$dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white;
$dropdownmenu-border: 1px solid $medium-gray;
// 19. Forms
// ---------
$fieldset-border: 1px solid $light-gray;
$fieldset-padding: $space-two;
$fieldset-margin: $space-one $zero;
$legend-padding: rem-calc(0 3);
$form-spacing: $space-normal;
$helptext-color: $color-body;
$helptext-font-size: $font-size-small;
$helptext-font-style: italic;
$input-prefix-color: $color-body;
$input-prefix-background: var(--b-100);
$input-prefix-border: 1px solid $color-border;
$input-prefix-padding: 0.625rem;
$form-label-color: $color-body;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $font-weight-medium;
$form-label-line-height: 1.8;
$select-background: $white;
$select-triangle-color: $dark-gray;
$select-radius: var(--border-radius-normal);
$input-color: $color-body;
$input-placeholder-color: $light-gray;
$input-font-family: inherit;
$input-font-size: $font-size-default;
$input-font-weight: $global-weight-normal;
$input-background: $white;
$input-background-focus: $white;
$input-background-disabled: $light-gray;
$input-border: 1px solid var(--s-200);
$input-border-focus: 1px solid lighten($primary-color, 15%);
$input-shadow: 0;
$input-shadow-focus: 0;
$input-cursor-disabled: not-allowed;
$input-transition: border-color 0.25s ease-in-out;
$input-number-spinners: true;
$input-radius: var(--border-radius-normal);
$form-button-radius: var(--border-radius-normal);
// 20. Label
// ---------
$label-background: $white;
$label-color: $black;
$label-color-alt: $black;
$label-palette: $foundation-palette;
$label-font-size: $font-size-mini;
$label-padding: $space-smaller $space-small;
$label-radius: var(--border-radius-small);
// 21. Media Object
// ----------------
$mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%;
// 22. Menu
// --------
$menu-margin: 0;
$menu-margin-nested: $space-medium;
$menu-item-padding: $space-slab;
$menu-item-color-active: $white;
$menu-item-background-active: $color-background;
$menu-icon-spacing: 0.15625rem;
$menu-item-background-hover: $light-gray;
$menu-border: $light-gray;
// 23. Meter
// ---------
$meter-height: 0.625rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
$meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-vertical-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
$offcanvas-push-zindex: 1;
$offcanvas-overlap-zindex: 10;
$offcanvas-reveal-zindex: 1;
$offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content';
// 25. Orbit
// ---------
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 0.75rem;
$orbit-bullet-margin: 0.0625rem;
$orbit-bullet-margin-top: 0.5rem;
$orbit-bullet-margin-bottom: 0.5rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 0.625rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 0.625rem;
$orbit-control-zindex: 10;
// 26. Pagination
// --------------
$pagination-font-size: rem-calc(14);
$pagination-margin-bottom: $global-margin;
$pagination-item-color: $black;
$pagination-item-padding: rem-calc(3 10);
$pagination-item-spacing: rem-calc(1);
$pagination-radius: $global-radius;
$pagination-item-background-hover: $light-gray;
$pagination-item-background-current: $primary-color;
$pagination-item-color-current: $white;
$pagination-item-color-disabled: $medium-gray;
$pagination-ellipsis-color: $black;
$pagination-mobile-items: false;
$pagination-mobile-current-item: false;
$pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 0.625rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
$progress-radius: $global-radius;
// 28. Responsive Embed
// --------------------
$responsive-embed-margin-bottom: rem-calc(16);
$responsive-embed-ratios: (default: 4 by 3,
widescreen: 16 by 9);
// 29. Reveal
// ----------
$reveal-background: $white;
$reveal-width: 600px;
$reveal-max-width: $global-width;
$reveal-padding: $global-padding;
$reveal-border: 1px solid $medium-gray;
$reveal-radius: $global-radius;
$reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45);
// 30. Slider
// ----------
$slider-width-vertical: 0.3125rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.3125rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 0.875rem;
$slider-handle-width: 0.875rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
// 31. Switch
// ----------
$switch-background: $light-gray;
$switch-background-active: $primary-color;
$switch-height: $space-two;
$switch-height-tiny: $space-slab;
$switch-height-small: $space-normal;
$switch-height-large: $space-large;
$switch-radius: $space-large;
$switch-margin: $global-margin;
$switch-paddle-background: $white;
$switch-paddle-offset: $space-micro;
$switch-paddle-radius: $space-large;
$switch-paddle-transition: all 0.15s ease-out;
// 32. Table
// ---------
$table-background: transparent;
$table-color-scale: 5%;
$table-border: 1px solid transparent;
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background,
$table-color-scale + $table-hover-scale);
$table-is-striped: false;
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
$table-head-row-hover: darken($table-head-background, $table-hover-scale);
$table-foot-background: smart-scale($table-background, $table-color-scale);
$table-foot-row-hover: darken($table-foot-background, $table-hover-scale);
$table-head-font-color: $body-font-color;
$table-foot-font-color: $body-font-color;
$show-header-for-stacked: false;
// 33. Tabs
// --------
$tab-margin: 0;
$tab-background: transparent;
$tab-background-active: transparent;
$tab-item-font-size: $font-size-small;
$tab-item-background-hover: transparent;
$tab-item-padding: $space-one $zero;
$tab-color: $primary-color;
$tab-active-color: $primary-color;
$tab-expand-max: 6;
$tab-content-background: transparent;
$tab-content-border: transparent;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 0.625rem;
// 34. Thumbnail
// -------------
$thumbnail-border: solid 4px $white;
$thumbnail-margin-bottom: $global-margin;
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
$thumbnail-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius;
// 35. Title Bar
// -------------
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.3125rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.15625rem;
// 36. Tooltip
// -----------
$has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.46875rem;
$tooltip-font-size: $font-size-mini;
$tooltip-pip-width: 0.46875rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 37. Top Bar
// -----------
$topbar-padding: 0.3125;
$topbar-background: $light-gray;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;
// Internal variable that contains the flex justifying options
$-zf-flex-justify: -zf-flex-justify($global-text-direction);
$menu-items-padding: $space-one;
$xy-grid: false;
@@ -2,11 +2,6 @@
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {
@include flex-align(center, middle);
display: flex;
}
.bottom-space-fix {
margin-bottom: auto;
}
@@ -1,5 +1,5 @@
@import '~dashboard/assets/scss/variables';
@import '~widget/assets/scss/mixins';
@import 'dashboard/assets/scss/variables';
@import 'widget/assets/scss/mixins';
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
@@ -89,9 +89,6 @@ $swift-ease-out-duration: .4s !default;
$swift-ease-out-function: cubic-bezier(0.37, 0, 0.63, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions
$transition-ease-in: all 0.250s ease-in;
@@ -15,36 +15,13 @@
@import 'variables';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'formulate';
@import 'date-picker';
@import 'foundation-sites/scss/foundation';
@include foundation-everything($flex: true);
@include foundation-prototype-text-utilities;
@include foundation-prototype-text-transformation;
@include foundation-prototype-text-decoration;
@include foundation-prototype-font-styling;
@include foundation-prototype-list-style-type;
@include foundation-prototype-rounded;
@include foundation-prototype-bordered;
@include foundation-prototype-shadow;
@include foundation-prototype-separator;
@include foundation-prototype-overflow;
@include foundation-prototype-display;
@include foundation-prototype-position;
@include foundation-prototype-border-box;
@include foundation-prototype-border-none;
@include foundation-prototype-sizing;
@include foundation-prototype-spacing;
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'rtl';
@import 'widgets/buttons';
@@ -52,15 +29,11 @@
@import 'widgets/conversation-card';
@import 'widgets/conversation-view';
@import 'widgets/forms';
@import 'widgets/login';
@import 'widgets/modal';
@import 'widgets/reply-box';
@import 'widgets/report';
@import 'widgets/search-box';
@import 'widgets/sidemenu';
@import 'widgets/snackbar';
@import 'widgets/states';
@import 'widgets/status-bar';
@import 'widgets/tabs';
@import 'widgets/woot-tables';
@@ -69,7 +42,6 @@
@import 'plugins/multiselect';
@import 'plugins/dropdown';
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
@@ -10,21 +10,14 @@
@import 'variables';
@import 'vue-multiselect/dist/vue-multiselect.min.css';
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'foundation-sites/scss/foundation';
@include foundation-prototype-spacing;
@include foundation-everything($flex: true);
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'widgets/buttons';
@import 'widgets/forms';
@@ -76,7 +76,11 @@
&.left {
.bubble {
@apply border border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-700 text-black-900 dark:text-slate-50 rounded-r-lg rounded-l mr-auto break-words;
@apply rounded-r-lg rounded-l mr-auto break-words;
&:not(.is-unsupported) {
@apply border border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-700 text-black-900 dark:text-slate-50
}
&.is-image {
@apply rounded-lg;
@@ -1,66 +0,0 @@
.auth-wrap {
width: 100%;
}
// Outside login wrapper
.login {
@include full-height;
overflow-y: auto;
padding-top: $space-larger * 1.2;
.login__hero {
margin-bottom: $space-larger;
.hero__logo {
width: 180px;
}
.hero__title {
font-weight: $font-weight-light;
margin-top: $space-larger;
}
.hero__sub {
color: $medium-gray;
font-size: $font-size-medium;
}
}
// Login box
.login-box {
@include background-white;
@include border-normal;
@include elegant-card;
border-radius: $space-smaller;
padding: $space-large;
label {
color: $color-gray;
font-size: $font-size-default;
input {
font-size: $font-size-default;
height: $space-larger;
padding: $space-slab;
}
.error {
font-size: $font-size-small;
}
}
.button {
height: $space-larger;
}
}
.sigin__footer {
font-size: $font-size-default;
padding: $space-medium;
> a {
font-weight: $font-weight-bold;
}
}
}
@@ -1,7 +1,5 @@
.modal-mask {
// @include flex;
// @include flex-align(center, middle);
@apply flex items-center justify-center bg-modal dark:bg-modal z-[9990] h-full left-0 fixed top-0 w-full;
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
}
.page-top-bar {
@@ -45,8 +43,6 @@
}
.modal-footer {
// @include flex;
// @include flex-align($x: flex-end, $y: middle);
@apply flex justify-end items-center py-2 px-0 gap-2;
&.justify-content-end {
@@ -8,7 +8,7 @@
.reply-box__top {
.icon {
color: $medium-gray;
color: var(--slate-500);
cursor: pointer;
font-size: $font-size-medium;
margin-right: $space-small;
@@ -34,7 +34,7 @@
resize: none;
}
> textarea {
>textarea {
@include ghost-input();
background: transparent;
margin: 0;
@@ -52,7 +52,7 @@
.reply-box__top {
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
>input {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
@@ -1,45 +1,10 @@
.side-menu {
i {
margin-right: var(--space-smaller);
min-width: var(--space-two);
}
}
.sidebar {
z-index: 1024 - 1;
//logo
.logo {
img {
max-height: 108px;
padding: $woot-logo-padding;
}
}
.nested {
a {
font-size: var(--font-size-small);
margin-bottom: var(--space-micro);
margin-top: var(--space-micro);
.inbox-icon {
display: inline-block;
margin-right: var(--space-micro);
min-width: var(--space-normal);
text-align: center;
}
}
}
}
// bottom-nav
.bottom-nav {
@include flex;
@include space-between-column;
@include border-normal-top;
flex-direction: column;
padding: var(--space-one) var(--space-normal) var(--space-one)
var(--space-one);
padding: var(--space-one) var(--space-normal) var(--space-one) var(--space-one);
position: relative;
&:hover {
@@ -519,6 +519,9 @@ export default {
},
},
watch: {
teamId() {
this.updateVirtualListProps('teamId', this.teamId);
},
activeTeam() {
this.resetAndFetchData();
},
@@ -13,7 +13,7 @@
<div class="flex items-center justify-between w-full">
<span
class="attribute-name"
:class="{ error: $v.editedValue.$error }"
:class="{ error: v$.editedValue.$error }"
>
{{ label }}
</span>
@@ -39,8 +39,8 @@
:type="inputType"
class="input-group-field"
autofocus="true"
:class="{ error: $v.editedValue.$error }"
@blur="$v.editedValue.$touch"
:class="{ error: v$.editedValue.$error }"
@blur="v$.editedValue.$touch"
@keyup.enter="onUpdate"
/>
<div class="input-group-button">
@@ -122,25 +122,37 @@
<script>
import { format, parseISO } from 'date-fns';
import { required, url } from 'vuelidate/lib/validators';
import { required, url } from '@vuelidate/validators';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import { isValidURL } from '../helper/URLHelper';
import customAttributeMixin from '../mixins/customAttributeMixin';
const DATE_FORMAT = 'yyyy-MM-dd';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
MultiselectDropdown,
},
mixins: [customAttributeMixin],
props: {
label: { type: String, required: true },
values: { type: Array, default: () => [] },
value: { type: [String, Number, Boolean], default: '' },
showActions: { type: Boolean, default: false },
attributeType: { type: String, default: 'text' },
attributeRegex: {
type: String,
default: null,
},
regexCue: { type: String, default: null },
regexEnabled: { type: Boolean, default: false },
attributeKey: { type: String, required: true },
contactId: { type: Number, default: null },
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
isEditing: false,
@@ -198,12 +210,17 @@ export default {
return this.isAttributeTypeLink ? 'url' : this.attributeType;
},
shouldShowErrorMessage() {
return this.$v.editedValue.$error;
return this.v$.editedValue.$error;
},
errorMessage() {
if (this.$v.editedValue.url) {
if (this.v$.editedValue.url) {
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_URL');
}
if (!this.v$.editedValue.regexValidation) {
return this.regexCue
? this.regexCue
: this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT');
}
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.REQUIRED');
},
},
@@ -221,7 +238,15 @@ export default {
};
}
return {
editedValue: { required },
editedValue: {
required,
regexValidation: value => {
return !(
this.attributeRegex &&
!this.getRegexp(this.attributeRegex).test(value)
);
},
},
};
},
mounted() {
@@ -259,8 +284,8 @@ export default {
this.attributeType === 'date'
? parseISO(this.editedValue)
: this.editedValue;
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.isEditing = false;
@@ -281,11 +306,14 @@ export default {
.checkbox-wrap {
@apply flex items-center;
}
.checkbox {
@apply my-0 mr-2 ml-0;
}
.attribute-name {
@apply w-full text-slate-800 dark:text-slate-100;
&.error {
@apply text-red-400 dark:text-red-500;
}
@@ -302,6 +330,7 @@ export default {
.value {
@apply bg-slate-50 dark:bg-slate-700 mb-0;
}
.edit-button {
@apply block;
}
@@ -311,10 +340,12 @@ export default {
::v-deep {
.selector-wrap {
@apply m-0 top-1;
.selector-name {
@apply ml-0;
}
}
.name {
@apply ml-0;
}
@@ -1,24 +1,33 @@
<template>
<transition name="network-notification-fade" tag="div">
<div v-show="showNotification" class="ui-notification-container">
<div class="ui-notification">
<fluent-icon icon="wifi-off" />
<p class="ui-notification-text">
{{
useInstallationName(
$t('NETWORK.NOTIFICATION.TEXT'),
globalConfig.installationName
)
}}
</p>
<woot-button variant="clear" size="small" @click="refreshPage">
{{ $t('NETWORK.BUTTON.REFRESH') }}
</woot-button>
<div v-show="showNotification" class="fixed top-4 left-2 z-50 group">
<div
class="flex items-center justify-between py-1 px-2 w-full rounded-lg shadow-lg bg-yellow-200 dark:bg-yellow-700 relative"
>
<fluent-icon
icon="wifi-off"
class="text-yellow-700/50 dark:text-yellow-50"
size="18"
/>
<span
class="text-xs tracking-wide font-medium px-2 text-yellow-700/70 dark:text-yellow-50"
>
{{ $t('NETWORK.NOTIFICATION.OFFLINE') }}
</span>
<woot-button
variant="smooth"
:title="$t('NETWORK.BUTTON.REFRESH')"
variant="clear"
size="small"
color-scheme="warning"
icon="dismiss-circle"
icon="arrow-clockwise"
class="visible transition-all duration-500 ease-in-out ml-1"
@click="refreshPage"
/>
<woot-button
variant="clear"
size="small"
color-scheme="warning"
icon="dismiss"
@click="closeNotification"
/>
</div>
@@ -47,7 +56,12 @@ export default {
mounted() {
window.addEventListener('offline', this.updateOnlineStatus);
window.bus.$on(BUS_EVENTS.WEBSOCKET_DISCONNECT, () => {
this.updateOnlineStatus({ type: 'offline' });
// TODO: Remove this after completing the conversation list refetching
// TODO: DIRTY FIX : CLEAN UP THIS WITH PROPER FIX, DELAYING THE RECONNECT FOR NOW
// THE CABLE IS FIRING IS VERY COMMON AND THUS INTERFERING USER EXPERIENCE
setTimeout(() => {
this.updateOnlineStatus({ type: 'offline' });
}, 4000);
});
},
@@ -72,33 +86,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/mixins';
.ui-notification-container {
max-width: 25rem;
position: absolute;
right: var(--space-normal);
top: var(--space-normal);
z-index: var(--z-index-very-high);
}
.ui-notification {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: var(--y-100);
border-radius: var(--border-radius-medium);
box-shadow: var(--shadow-large);
min-width: 15rem;
padding: var(--space-normal);
}
.ui-notification-text {
margin: 0 var(--space-small);
}
</style>
+15 -15
View File
@@ -1,29 +1,29 @@
/* eslint no-plusplus: 0 */
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
import Bar from './widgets/chart/BarChart';
import Button from './ui/WootButton';
import Code from './Code';
import ColorPicker from './widgets/ColorPicker';
import Button from './ui/WootButton.vue';
import Code from './Code.vue';
import ColorPicker from './widgets/ColorPicker.vue';
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
import ContextMenu from './ui/ContextMenu.vue';
import DeleteModal from './widgets/modal/DeleteModal.vue';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import FeatureToggle from './widgets/FeatureToggle';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import FeatureToggle from './widgets/FeatureToggle.vue';
import HorizontalBar from './widgets/chart/HorizontalBarChart';
import Input from './widgets/forms/Input.vue';
import PhoneInput from './widgets/forms/PhoneInput.vue';
import Label from './ui/Label';
import LoadingState from './widgets/LoadingState';
import Modal from './Modal';
import ModalHeader from './ModalHeader';
import ReportStatsCard from './widgets/ReportStatsCard';
import SidemenuIcon from './SidemenuIcon';
import Spinner from 'shared/components/Spinner';
import SubmitButton from './buttons/FormSubmitButton';
import Label from './ui/Label.vue';
import LoadingState from './widgets/LoadingState.vue';
import Modal from './Modal.vue';
import ModalHeader from './ModalHeader.vue';
import ReportStatsCard from './widgets/ReportStatsCard.vue';
import SidemenuIcon from './SidemenuIcon.vue';
import Spinner from 'shared/components/Spinner.vue';
import SubmitButton from './buttons/FormSubmitButton.vue';
import Tabs from './ui/Tabs/Tabs';
import TabsItem from './ui/Tabs/TabsItem';
import TabsItem from './ui/Tabs/TabsItem.vue';
import Thumbnail from './widgets/Thumbnail.vue';
const WootUIKit = {
@@ -19,13 +19,13 @@
<form class="flex flex-col w-full" @submit.prevent="addAccount">
<div class="w-full">
<label :class="{ error: $v.accountName.$error }">
<label :class="{ error: v$.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
v-model.trim="accountName"
type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch"
@input="v$.accountName.$touch"
/>
</label>
</div>
@@ -33,8 +33,8 @@
<div class="w-full">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
$v.accountName.$invalid ||
v$.accountName.$invalid ||
v$.accountName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
@@ -49,9 +49,10 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
@@ -65,6 +66,9 @@ export default {
default: true,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
accountName: '',
@@ -56,7 +56,7 @@
class="bg-slate-50 dark:bg-slate-700 rounded text-xxs font-medium mx-1 py-0 px-1"
:class="
isCountZero
? `text-slate-300 dark:text-slate-700`
? `text-slate-300 dark:text-slate-500`
: `text-slate-700 dark:text-slate-50`
"
>
@@ -1,4 +1,6 @@
export default {
import { defineComponent } from 'vue';
export default defineComponent({
name: 'WootTabs',
props: {
index: {
@@ -73,19 +75,25 @@ export default {
});
const leftButton = this.createScrollButton(createElement, 'left');
const rightButton = this.createScrollButton(createElement, 'right');
return (
<div
class={{
return createElement(
'div',
{
class: {
'tabs--container--with-border': this.border,
'tabs--container': true,
}}
>
{leftButton}
<ul class={{ tabs: true, 'tabs--with-scroll': this.hasScroll }}>
{Tabs}
</ul>
{rightButton}
</div>
},
},
[
leftButton,
createElement(
'ul',
{
class: { tabs: true, 'tabs--with-scroll': this.hasScroll },
},
Tabs
),
rightButton,
]
);
},
};
});
@@ -12,11 +12,11 @@
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:class="{ error: v$.value.$error }"
:placeholder="
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
"
@blur="$v.value.$touch"
@blur="v$.value.$touch"
/>
</div>
<div class="flex flex-row justify-between gap-2 py-2 px-0 w-full">
@@ -27,7 +27,7 @@
<woot-button variant="clear" @click.prevent="onDismiss">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
</woot-button>
<woot-button :is-disabled="$v.value.$invalid">
<woot-button :is-disabled="v$.value.$invalid">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
</woot-button>
</div>
@@ -37,15 +37,19 @@
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import aiMixin from 'dashboard/mixins/aiMixin';
import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [aiMixin, alertMixin, uiSettingsMixin],
setup() {
return { v$: useVuelidate() };
},
data() {
return {
value: '',
@@ -53,8 +53,8 @@ export default {
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins';
@import 'dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/mixins';
.colorpicker {
position: relative;
@@ -84,7 +84,10 @@ import {
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
import { replaceVariablesInMessage } from '@chatwoot/utils';
import {
replaceVariablesInMessage,
createTypingIndicator,
} from '@chatwoot/utils';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { uploadFile } from 'dashboard/helper/uploadHelper';
@@ -140,6 +143,15 @@ export default {
},
data() {
return {
typingIndicator: createTypingIndicator(
() => {
this.$emit('typing-on');
},
() => {
this.$emit('typing-off');
},
TYPING_INDICATOR_IDLE_TIME
),
showUserMentions: false,
showCannedMenu: false,
showVariables: false,
@@ -638,15 +650,6 @@ export default {
hideMentions() {
this.showUserMentions = false;
},
resetTyping() {
this.$emit('typing-off');
this.idleTimer = null;
},
turnOffIdleTimer() {
if (this.idleTimer) {
clearTimeout(this.idleTimer);
}
},
handleLineBreakWhenEnterToSendEnabled(event) {
if (
hasPressedEnterAndNotCmdOrShift(event) &&
@@ -666,14 +669,7 @@ export default {
}
},
onKeyup() {
if (!this.idleTimer) {
this.$emit('typing-on');
}
this.turnOffIdleTimer();
this.idleTimer = setTimeout(
() => this.resetTyping(),
TYPING_INDICATOR_IDLE_TIME
);
this.typingIndicator.start();
this.updateImgToolbarOnDelete();
},
onKeydown(event) {
@@ -685,8 +681,7 @@ export default {
}
},
onBlur() {
this.turnOffIdleTimer();
this.resetTyping();
this.typingIndicator.stop();
this.$emit('blur');
},
onFocus() {
@@ -710,10 +705,11 @@ export default {
</script>
<style lang="scss">
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss';
@import '@chatwoot/prosemirror-schema/src/styles/base.scss';
.ProseMirror-menubar-wrapper {
@apply flex flex-col;
.ProseMirror-menubar {
min-height: var(--space-two) !important;
@apply -ml-2.5 pb-0 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100;
@@ -722,6 +718,7 @@ export default {
@apply bg-slate-75 dark:bg-slate-800;
}
}
> .ProseMirror {
@apply p-0 break-words text-slate-800 dark:text-slate-100;
@@ -101,10 +101,10 @@
<transition name="modal-fade">
<div
v-show="$refs.upload && $refs.upload.dropActive"
class="fixed top-0 bottom-0 left-0 right-0 z-20 flex flex-col items-center justify-center w-full h-full gap-2 text-slate-600 dark:text-slate-200 bg-white_transparent dark:bg-black_transparent"
class="fixed top-0 bottom-0 left-0 right-0 z-20 flex flex-col items-center justify-center w-full h-full gap-2 text-slate-900 dark:text-slate-50 bg-modal-backdrop-light dark:bg-modal-backdrop-dark"
>
<fluent-icon icon="cloud-backup" size="40" />
<h4 class="page-sub-title text-slate-600 dark:text-slate-200">
<h4 class="page-sub-title text-slate-900 dark:text-slate-50">
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
</h4>
</div>
@@ -1,5 +1,9 @@
<template>
<mention-box :items="items" @mention-select="handleMentionClick">
<mention-box
v-if="items.length"
:items="items"
@mention-select="handleMentionClick"
>
<template slot-scope="{ item }">
<strong>{{ item.label }}</strong> - {{ item.description }}
</template>
@@ -41,7 +41,7 @@
:show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:grouped-filters="true"
:v="$v.appliedFilters.$each[i]"
:v="v$.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)"
/>
@@ -80,7 +80,7 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required, requiredIf } from 'vuelidate/lib/validators';
import { required, requiredIf } from '@vuelidate/validators';
import FilterInputBox from '../FilterInput/Index.vue';
import languages from './advancedFilterItems/languages';
import countries from 'shared/constants/countries.js';
@@ -89,6 +89,7 @@ import { filterAttributeGroups } from './advancedFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -117,6 +118,9 @@ export default {
default: false,
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
appliedFilters: {
required,
@@ -347,8 +351,8 @@ export default {
}
},
submitFilterQuery() {
this.$v.$touch();
if (this.$v.$invalid) return;
this.v$.$touch();
if (this.v$.$invalid) return;
this.$store.dispatch(
'setConversationFilters',
JSON.parse(JSON.stringify(this.appliedFilters))
@@ -45,14 +45,14 @@
}}</label>
</div>
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
<label :class="{ error: $v.email.$error }">
<label :class="{ error: v$.email.$error }">
<input
v-model.trim="email"
type="text"
:placeholder="$t('EMAIL_TRANSCRIPT.FORM.EMAIL.PLACEHOLDER')"
@input="$v.email.$touch"
@input="v$.email.$touch"
/>
<span v-if="$v.email.$error" class="message">
<span v-if="v$.email.$error" class="message">
{{ $t('EMAIL_TRANSCRIPT.FORM.EMAIL.ERROR') }}
</span>
</label>
@@ -73,8 +73,10 @@
</template>
<script>
import { required, minLength, email } from 'vuelidate/lib/validators';
import { required, minLength, email } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
props: {
@@ -87,6 +89,9 @@ export default {
default: () => ({}),
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
email: '',
@@ -108,7 +113,7 @@ export default {
isFormValid() {
if (this.selectedType) {
if (this.sentToOtherEmailAddress) {
return !!this.email && !this.$v.email.$error;
return !!this.email && !this.v$.email.$error;
}
return true;
}
@@ -1,7 +1,10 @@
<template>
<li v-if="shouldRenderMessage" :id="`message${data.id}`" :class="alignBubble">
<div :class="wrapClass">
<div v-if="isFailed && !hasOneDayPassed" class="message-failed--alert">
<div
v-if="isFailed && !hasOneDayPassed && !isAnEmailInbox"
class="message-failed--alert"
>
<woot-button
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
size="tiny"
@@ -29,8 +32,19 @@
:message-type="data.message_type"
:parent-has-attachments="hasAttachments"
/>
<div v-if="isUnsupported">
<template v-if="isAFacebookInbox && isInstagram">
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_INSTAGRAM') }}
</template>
<template v-else-if="isAFacebookInbox">
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE_FACEBOOK') }}
</template>
<template v-else>
{{ $t('CONVERSATION.UNSUPPORTED_MESSAGE') }}
</template>
</div>
<bubble-text
v-if="data.content"
v-else-if="data.content"
:message="message"
:is-email="isEmailContentType"
:display-quoted-button="displayQuotedButton"
@@ -176,6 +190,14 @@ export default {
type: Boolean,
default: false,
},
isAFacebookInbox: {
type: Boolean,
default: false,
},
isInstagram: {
type: Boolean,
default: false,
},
isAWhatsAppChannel: {
type: Boolean,
default: false,
@@ -184,6 +206,10 @@ export default {
type: Boolean,
default: false,
},
isAnEmailInbox: {
type: Boolean,
default: false,
},
inboxSupportsReplyTo: {
type: Object,
default: () => ({}),
@@ -219,6 +245,7 @@ export default {
this.hasAttachments ||
this.data.content ||
this.isEmailContentType ||
this.isUnsupported ||
this.isAnIntegrationMessage
);
},
@@ -407,6 +434,7 @@ export default {
return {
bubble: this.isBubble,
'is-private': this.data.private,
'is-unsupported': this.isUnsupported,
'is-image': this.hasMediaAttachment('image'),
'is-video': this.hasMediaAttachment('video'),
'is-text': this.hasText,
@@ -415,6 +443,9 @@ export default {
'is-email': this.isEmailContentType,
};
},
isUnsupported() {
return this.contentAttributes.is_unsupported ?? false;
},
isPending() {
return this.data.status === MESSAGE_STATUS.PROGRESS;
},
@@ -426,11 +457,7 @@ export default {
return !this.sender.type || this.sender.type === 'agent_bot';
},
shouldShowContextMenu() {
return !(this.isFailed || this.isPending);
},
errorMessage() {
const { meta } = this.data;
return meta ? meta.error : '';
return !(this.isFailed || this.isPending || this.isUnsupported);
},
showAvatar() {
if (this.isOutgoing || this.isTemplate) {
@@ -532,6 +559,14 @@ export default {
> .bubble {
@apply min-w-[128px];
&.is-unsupported {
@apply text-xs max-w-[300px] border-dashed border border-slate-200 text-slate-600 dark:text-slate-200 bg-slate-50 dark:bg-slate-700 dark:border-slate-500;
.message-text--metadata .time {
@apply text-slate-400 dark:text-slate-300;
}
}
&.is-image,
&.is-video {
@apply p-0 overflow-hidden;
@@ -544,10 +579,12 @@ export default {
> video {
@apply rounded-lg;
}
> video {
@apply h-full w-full object-cover;
}
}
.video {
@apply h-[11.25rem];
}
@@ -562,9 +599,11 @@ export default {
.file--icon {
@apply text-woot-400 dark:text-woot-400;
}
.text-block-title {
@apply text-slate-700 dark:text-slate-700;
}
.download.button {
@apply text-woot-400 dark:text-woot-400;
}
@@ -573,6 +612,7 @@ export default {
&.is-private.is-text > .message-text__wrap .link {
@apply text-woot-600 dark:text-woot-200;
}
&.is-private.is-text > .message-text__wrap .prosemirror-mention-node {
@apply font-bold bg-none rounded-sm p-0 bg-yellow-100 dark:bg-yellow-700 text-slate-700 dark:text-slate-25 underline;
}
@@ -583,6 +623,7 @@ export default {
.message-text--metadata .time {
@apply text-violet-50 dark:text-violet-50;
}
&.is-private .message-text--metadata .time {
@apply text-slate-400 dark:text-slate-400;
}
@@ -32,6 +32,9 @@
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-an-email-inbox="isAnEmailChannel"
:is-instagram="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
@@ -54,6 +57,8 @@
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-instagram-dm="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
@@ -283,6 +288,9 @@ export default {
unreadMessageCount() {
return this.currentChat.unread_count || 0;
},
isInstagramDM() {
return this.conversationType === 'instagram_direct_message';
},
inboxSupportsReplyTo() {
const incoming = this.inboxHasFeature(INBOX_FEATURES.REPLY_TO);
const outgoing =
@@ -164,7 +164,7 @@ import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel
import ReplyEmailHead from './ReplyEmailHead.vue';
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
import ArticleSearchPopover from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue';
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert';
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert.vue';
import Banner from 'dashboard/components/ui/Banner.vue';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
@@ -198,7 +198,7 @@ import {
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { LocalStorage } from 'shared/helpers/localStorage';
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
export default {
components: {
@@ -271,6 +271,11 @@ export default {
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
currentContact() {
return this.$store.getters['contacts/getContact'](
this.currentChat.meta.sender.id
);
},
shouldShowReplyToMessage() {
return (
this.inReplyTo?.id &&
@@ -509,6 +514,7 @@ export default {
messageVariables() {
const variables = getMessageVariables({
conversation: this.currentChat,
contact: this.currentContact,
});
return variables;
},
@@ -1202,6 +1208,7 @@ export default {
@apply bg-yellow-50 dark:bg-yellow-200;
}
}
.send-button {
@apply mb-0;
}
@@ -1222,6 +1229,7 @@ export default {
.emoji-dialog--rtl {
@apply left-[unset] -right-80;
&::before {
transform: rotate(90deg);
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
@@ -1,15 +1,15 @@
<template>
<div>
<div v-if="toEmails">
<div class="input-group small" :class="{ error: $v.toEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.toEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.toEmailsVal.$model"
v-model.trim="v$.toEmailsVal.$model"
type="text"
:class="{ error: $v.toEmailsVal.$error }"
:class="{ error: v$.toEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur"
/>
@@ -17,15 +17,15 @@
</div>
</div>
<div class="input-group-wrap">
<div class="input-group small" :class="{ error: $v.ccEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.ccEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.ccEmailsVal.$model"
v-model.trim="v$.ccEmailsVal.$model"
type="text"
:class="{ error: $v.ccEmailsVal.$error }"
:class="{ error: v$.ccEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur"
/>
@@ -39,20 +39,20 @@
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC') }}
</woot-button>
</div>
<span v-if="$v.ccEmailsVal.$error" class="message">
<span v-if="v$.ccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }}
</span>
</div>
<div v-if="showBcc" class="input-group-wrap">
<div class="input-group small" :class="{ error: $v.bccEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.bccEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.bccEmailsVal.$model"
v-model.trim="v$.bccEmailsVal.$model"
type="text"
:class="{ error: $v.bccEmailsVal.$error }"
:class="{ error: v$.bccEmailsVal.$error }"
:placeholder="
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
"
@@ -60,7 +60,7 @@
/>
</div>
</div>
<span v-if="$v.bccEmailsVal.$error" class="message">
<span v-if="v$.bccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.ERROR') }}
</span>
</div>
@@ -137,7 +137,7 @@ export default {
this.showBcc = true;
},
onBlur() {
this.$v.$touch();
this.v$.$touch();
this.$emit('update:bccEmails', this.bccEmailsVal);
this.$emit('update:ccEmails', this.ccEmailsVal);
this.$emit('update:toEmails', this.toEmailsVal);
@@ -149,12 +149,14 @@ export default {
.input-group-wrap .message {
@apply text-sm text-red-500 dark:text-red-500;
}
.input-group {
@apply border-b border-solid border-slate-75 dark:border-slate-700 my-1;
.input-group-label {
@apply border-transparent bg-transparent text-xs font-semibold pl-0;
}
.input-group-field::v-deep input {
@apply mb-0 border-transparent;
}
@@ -162,6 +164,7 @@ export default {
.input-group.error {
@apply border-b-red-500 dark:border-b-red-500;
.input-group-label {
@apply text-red-500 dark:text-red-500;
}
@@ -1,5 +1,10 @@
<template>
<mention-box :items="items" @mention-select="handleVariableClick">
<mention-box
v-if="items.length"
type="variable"
:items="items"
@mention-select="handleVariableClick"
>
<template slot-scope="{ item }">
<span class="text-capitalize variable--list-label">
{{ item.description }}
@@ -10,6 +15,7 @@
</template>
<script>
import { mapGetters } from 'vuex';
import { MESSAGE_VARIABLES } from 'shared/constants/messages';
import MentionBox from '../mentions/MentionBox.vue';
@@ -22,7 +28,16 @@ export default {
},
},
computed: {
...mapGetters({
customAttributes: 'attributes/getAttributes',
}),
items() {
return [
...this.standardAttributeVariables,
...this.customAttributeVariables,
];
},
standardAttributeVariables() {
return MESSAGE_VARIABLES.filter(variable => {
return (
variable.label.includes(this.searchKey) ||
@@ -34,6 +49,20 @@ export default {
description: variable.label,
}));
},
customAttributeVariables() {
return this.customAttributes.map(attribute => {
const attributePrefix =
attribute.attribute_model === 'conversation_attribute'
? 'conversation'
: 'contact';
return {
label: `${attributePrefix}.custom_attribute.${attribute.attribute_key}`,
key: `${attributePrefix}.custom_attribute.${attribute.attribute_key}`,
description: attribute.attribute_description,
};
});
},
},
methods: {
handleVariableClick(item = {}) {
@@ -45,7 +45,9 @@ const allKeysRequired = value => {
const keys = Object.keys(value);
return keys.every(key => value[key]);
};
import { requiredIf } from 'vuelidate/lib/validators';
import { useVuelidate } from '@vuelidate/core';
import { requiredIf } from '@vuelidate/validators';
export default {
props: {
template: {
@@ -53,6 +55,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
processedParams: {
requiredIfKeysPresent: requiredIf('variables'),
@@ -149,9 +154,11 @@ footer {
@apply ml-2.5;
}
}
.error {
@apply bg-red-100 dark:bg-red-100 rounded-md text-red-800 dark:text-red-800 p-2.5 text-center;
}
.template-input {
@apply bg-slate-25 dark:bg-slate-900 text-slate-700 dark:text-slate-100;
}
@@ -46,7 +46,7 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/variables';
.file {
display: flex;
@@ -1,4 +1,4 @@
import emailValidator from 'vuelidate/lib/validators/email';
import { email as emailValidator } from '@vuelidate/validators';
export const validEmailsByComma = value => {
if (!value.length) return true;
@@ -5,7 +5,7 @@
class="cursor-pointer py-2 pr-1.5 pl-2 rounded-tl-md rounded-bl-md flex items-center justify-center gap-1.5 bg-slate-25 dark:bg-slate-700 h-10 w-14"
@click="toggleCountryDropdown"
>
<h5 v-if="activeCountry.emoji" class="mb-0">
<h5 v-if="activeCountry" class="mb-0">
{{ activeCountry.emoji }}
</h5>
<fluent-icon v-else icon="globe" class="fluent-icon" size="16" />
@@ -1,20 +1,40 @@
<template>
<div v-if="items.length" ref="mentionsListContainer" class="mention--box">
<div
ref="mentionsListContainer"
class="bg-white dark:bg-slate-800 rounded-md overflow-auto absolute w-full z-20 pb-0 shadow-md left-0 bottom-full max-h-[9.75rem] border border-solid border-slate-100 dark:border-slate-700 mention--box"
>
<ul class="vertical dropdown menu">
<woot-dropdown-item
v-for="(item, index) in items"
:id="`mention-item-${index}`"
:key="item.key"
class="!mb-0"
@mouseover="onHover(index)"
>
<woot-button
class="canned-item__button"
:variant="index === selectedIndex ? '' : 'clear'"
:class="{ active: index === selectedIndex }"
<button
class="flex group flex-col gap-0.5 overflow-hidden cursor-pointer items-start py-2.5 px-2.5 justify-center w-full h-full text-left hover:bg-woot-50 dark:hover:bg-woot-800 border-b border-solid border-slate-100 dark:border-slate-700"
:class="{
' bg-woot-25 dark:bg-woot-800': index === selectedIndex,
}"
@click="onListItemSelection(index)"
>
<strong>{{ item.label }}</strong> - {{ item.description }}
</woot-button>
<p
class="text-slate-900 dark:text-slate-100 group-hover:text-woot-500 dark:group-hover:text-woot-500 font-medium mb-0 text-sm overflow-hidden text-ellipsis whitespace-nowrap min-w-0 max-w-full"
:class="{
'text-woot-500 dark:text-woot-500': index === selectedIndex,
}"
>
{{ item.description }}
</p>
<p
class="text-slate-500 dark:text-slate-300 group-hover:text-woot-500 dark:group-hover:text-woot-500 mb-0 text-xs overflow-hidden text-ellipsis whitespace-nowrap min-w-0 max-w-full"
:class="{
'text-woot-500 dark:text-woot-500': index === selectedIndex,
}"
>
{{ variableKey(item) }}
</p>
</button>
</woot-dropdown-item>
</ul>
</div>
@@ -29,6 +49,10 @@ export default {
type: Array,
default: () => {},
},
type: {
type: String,
default: 'canned',
},
},
data() {
return {
@@ -73,28 +97,17 @@ export default {
onSelect() {
this.$emit('mention-select', this.items[this.selectedIndex]);
},
variableKey(item = {}) {
return this.type === 'variable' ? `{{${item.label}}}` : `/${item.label}`;
},
},
};
</script>
<style scoped lang="scss">
.mention--box {
@apply bg-white dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-20 pt-2 px-2 pb-0 shadow-md left-0 bottom-full max-h-[9.75rem] border-t border-solid border-slate-75 dark:border-slate-800;
.dropdown-menu__item:last-child {
@apply pb-1;
}
.active {
@apply text-white dark:text-white;
&:hover {
@apply bg-woot-700 dark:bg-woot-700;
}
}
.button {
@apply transition-none h-8 leading-[1.4];
.dropdown-menu__item:last-child > button {
@apply border-0;
}
}
@@ -6,12 +6,12 @@
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:class="{ error: v$.value.$error }"
:placeholder="confirmPlaceHolderText"
@blur="$v.value.$touch"
@blur="v$.value.$touch"
/>
<div class="button-wrapper">
<woot-button color-scheme="alert" :is-disabled="$v.value.$invalid">
<woot-button color-scheme="alert" :is-disabled="v$.value.$invalid">
{{ confirmText }}
</woot-button>
<woot-button class="clear" @click.prevent="closeModal">
@@ -23,14 +23,14 @@
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import Modal from '../../Modal.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
Modal,
},
props: {
show: {
type: Boolean,
@@ -61,6 +61,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
value: '',
@@ -10,6 +10,7 @@ export class DataManager {
async initDb() {
if (this.db) return this.db;
const dbName = `cw-store-${this.accountId}`;
this.db = await openDB(`cw-store-${this.accountId}`, DATA_VERSION, {
upgrade(db) {
db.createObjectStore('cache-keys');
@@ -19,6 +20,13 @@ export class DataManager {
},
});
// Store the database name in LocalStorage
const dbNames = JSON.parse(localStorage.getItem('cw-idb-names') || '[]');
if (!dbNames.includes(dbName)) {
dbNames.push(dbName);
localStorage.setItem('cw-idb-names', JSON.stringify(dbNames));
}
return this.db;
}
@@ -47,6 +47,8 @@ export const getCustomFields = ({ standardFields, customAttributes }) => {
type: attribute.attribute_display_type,
values: attribute.attribute_values,
field_type: attribute.attribute_model,
regex_pattern: attribute.regex_pattern,
regex_cue: attribute.regex_cue,
required: false,
enabled: false,
});
@@ -44,4 +44,18 @@ export default {
created_at: '2021-11-29T10:20:04.563Z',
},
],
customAttributesWithRegex: [
{
id: 2,
attribute_description: 'Test contact Attribute',
attribute_display_name: 'Test contact Attribute',
attribute_display_type: 'text',
attribute_key: 'test_contact_attribute',
attribute_model: 'contact_attribute',
attribute_values: Array(0),
created_at: '2023-09-20T10:20:04.563Z',
regex_pattern: '^w+$',
regex_cue: 'It should be a combination of alphabets and numbers',
},
],
};
@@ -5,7 +5,8 @@ import {
} from '../preChat';
import inboxFixture from './inboxFixture';
const { customFields, customAttributes } = inboxFixture;
const { customFields, customAttributes, customAttributesWithRegex } =
inboxFixture;
describe('#Pre chat Helpers', () => {
describe('getPreChatFields', () => {
it('should return correct pre-chat fields form options passed', () => {
@@ -27,7 +28,6 @@ describe('#Pre chat Helpers', () => {
placeholder: 'Please enter your email address',
type: 'email',
field_type: 'standard',
required: false,
enabled: false,
},
@@ -71,6 +71,26 @@ describe('#Pre chat Helpers', () => {
values: [],
},
]);
expect(
getCustomFields({
standardFields: { pre_chat_fields: customFields.pre_chat_fields },
customAttributes: customAttributesWithRegex,
})
).toEqual([
{
enabled: false,
label: 'Test contact Attribute',
placeholder: 'Test contact Attribute',
name: 'test_contact_attribute',
required: false,
field_type: 'contact_attribute',
type: 'text',
values: [],
regex_pattern: '^w+$',
regex_cue: 'It should be a combination of alphabets and numbers',
},
]);
});
});
});
@@ -1,18 +1,18 @@
{
"FILTER": {
"TITLE": "Filter Conversations",
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
"TITLE": "Filter conversations",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "Add Filter",
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBMIT_BUTTON_LABEL": "Apply filters",
"UPDATE_BUTTON_LABEL": "Update folder",
"CANCEL_BUTTON_LABEL": "Cancel",
"CLEAR_BUTTON_LABEL": "Clear Filters",
"EMPTY_VALUE_ERROR": "Value is required",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"EMPTY_VALUE_ERROR": "Value is required.",
"TOOLTIP_LABEL": "Filter conversations",
"QUERY_DROPDOWN_LABELS": {
"AND": "AND",
@@ -36,44 +36,44 @@
},
"ATTRIBUTES": {
"STATUS": "Status",
"ASSIGNEE_NAME": "Assignee Name",
"INBOX_NAME": "Inbox Name",
"TEAM_NAME": "Team Name",
"CONVERSATION_IDENTIFIER": "Conversation Identifier",
"CAMPAIGN_NAME": "Campaign Name",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "Inbox name",
"TEAM_NAME": "Team name",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"LABELS": "Labels",
"BROWSER_LANGUAGE": "Browser Language",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "Priority",
"COUNTRY_NAME": "Country Name",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Last Activity"
"CREATED_AT": "Created at",
"LAST_ACTIVITY": "Last activity"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Custom Attributes"
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "Name is required.",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Cancel",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
"SUCCESS_MESSAGE": "Folder created successfully.",
"ERROR_MESSAGE": "Error while creating folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
"SUCCESS_MESSAGE": "Segment created successfully.",
"ERROR_MESSAGE": "Error while creating segment."
}
},
"EDIT": {
@@ -83,19 +83,19 @@
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Confirm Deletion",
"TITLE": "Confirm deletion",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Yes, Delete",
"NO": "No, Keep it"
"YES": "Yes, delete",
"NO": "No, keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
"SUCCESS_MESSAGE": "Folder deleted successfully.",
"ERROR_MESSAGE": "Error while deleting folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
"SUCCESS_MESSAGE": "Segment deleted successfully.",
"ERROR_MESSAGE": "Error while deleting segment."
}
}
}
@@ -1,73 +1,73 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading Editor...",
"HEADER_BTN_TXT": "Add Bot Configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
"NAME": {
"LABEL": "Bot Name",
"PLACEHOLDER": "Give your bot a name",
"ERROR": "Bot name is required"
"LABEL": "Bot name",
"PLACEHOLDER": "Name your bot.",
"ERROR": "Bot name is required."
},
"DESCRIPTION": {
"LABEL": "Bot Description",
"LABEL": "Bot description",
"PLACEHOLDER": "What does this bot do?"
},
"BOT_CONFIG": {
"ERROR": "Please enter your CSML bot configuration above",
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
"ERROR": "Please enter your CSML bot configuration above.",
"API_ERROR": "Your CSML configuration is invalid. Please fix it and try again."
},
"SUBMIT": "Validate and save"
},
"BOT_CONFIGURATION": {
"TITLE": "Select an agent bot",
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
"DESC": "Assign an Agent Bot to your inbox. They can handle initial conversations and transfer them to a live agent when necessary.",
"SUBMIT": "Update",
"DISCONNECT": "Disconnect Bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
"SELECT_PLACEHOLDER": "Select Bot"
"DISCONNECT": "Disconnect bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot.",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot.",
"ERROR_MESSAGE": "Could not update the agent bot. Please try again.",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot. Please try again.",
"SELECT_PLACEHOLDER": "Select bot"
},
"ADD": {
"TITLE": "Configure new bot",
"CANCEL_BUTTON_TEXT": "Cancel",
"API": {
"SUCCESS_MESSAGE": "Bot added successfully",
"ERROR_MESSAGE": "Could not add bot, Please try again later"
"SUCCESS_MESSAGE": "Bot added successfully.",
"ERROR_MESSAGE": "Could not add bot. Please try again later."
}
},
"LIST": {
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
"LOADING": "Fetching Bots...",
"TYPE": "Bot Type"
"404": "No bots found. You can create a bot by clicking the 'Configure new bot' button ↗",
"LOADING": "Fetching bots...",
"TYPE": "Bot type"
},
"DELETE": {
"BUTTON_TEXT": "Delete",
"TITLE": "Delete Bot",
"TITLE": "Delete bot",
"SUBMIT": "Delete",
"CANCEL_BUTTON_TEXT": "Cancel",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible.",
"API": {
"SUCCESS_MESSAGE": "Bot deleted successfully",
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
"SUCCESS_MESSAGE": "Bot deleted successfully.",
"ERROR_MESSAGE": "Could not delete bot. Please try again."
}
},
"EDIT": {
"BUTTON_TEXT": "Edit",
"LOADING": "Fetching Bots...",
"TITLE": "Edit Bot",
"LOADING": "Fetching bots...",
"TITLE": "Edit bot",
"CANCEL_BUTTON_TEXT": "Cancel",
"API": {
"SUCCESS_MESSAGE": "Bot updated successfully",
"ERROR_MESSAGE": "Could not update bot, Please try again later"
"SUCCESS_MESSAGE": "Bot updated successfully.",
"ERROR_MESSAGE": "Could not update bot. Please try again."
}
},
"TYPES": {
"WEBHOOK": "Webhook Bot",
"CSML": "CSML Bot"
"WEBHOOK": "Webhook bot",
"CSML": "CSML bot"
}
}
}
@@ -1,40 +1,40 @@
{
"BULK_ACTION": {
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
"AGENT_SELECT_LABEL": "Select Agent",
"AGENT_SELECT_LABEL": "Select agent",
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
"GO_BACK_LABEL": "Go back",
"ASSIGN_LABEL": "Assign",
"YES": "Yes",
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
"ASSIGN_AGENT_TOOLTIP": "Assign agent",
"ASSIGN_TEAM_TOOLTIP": "Assign team",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully",
"RESOLVE_FAILED": "Failed to resolve conversations, please try again",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully.",
"ASSIGN_FAILED": "Failed to assign conversations. Please try again.",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully.",
"RESOLVE_FAILED": "Failed to resolve conversations. Please try again.",
"ALL_CONVERSATIONS_SELECTED_ALERT": "Conversations visible on this page are only selected.",
"AGENT_LIST_LOADING": "Loading Agents",
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations, please try again"
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
"LABELS": {
"ASSIGN_LABELS": "Assign Labels",
"ASSIGN_LABELS": "Assign labels",
"NO_LABELS_FOUND": "No labels found for",
"ASSIGN_SELECTED_LABELS": "Assign selected labels",
"ASSIGN_SUCCESFUL": "Labels assigned successfully",
"ASSIGN_FAILED": "Failed to assign labels, please try again"
"ASSIGN_SUCCESFUL": "Labels assigned successfully.",
"ASSIGN_FAILED": "Failed to assign labels. Please try again."
},
"TEAMS": {
"TEAM_SELECT_LABEL": "Select Team",
"TEAM_SELECT_LABEL": "Select team",
"NONE": "None",
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
"ASSIGN_FAILED": "Failed to assign team, please try again"
"ASSIGN_SELECTED_TEAMS": "Assign selected team.",
"ASSIGN_SUCCESFUL": "Teams assiged successfully.",
"ASSIGN_FAILED": "Failed to assign team. Please try again."
}
}
}
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "Canned Responses",
"HEADER_BTN_TXT": "Add Canned Response",
"LOADING": "Fetching Canned Responses",
"SEARCH_404": "There are no items matching this query",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"SEARCH_404": "There are no items matching this query.",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"LIST": {
"404": "There are no canned responses available in this account.",
"TITLE": "Manage canned responses",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to tickets.",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TABLE_HEADER": [
"Short Code",
"Short code",
"Content",
"Actions"
]
},
"ADD": {
"TITLE": "Add Canned Response",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"CANCEL_BUTTON_TEXT": "Cancel",
"FORM": {
"SHORT_CODE": {
"LABEL": "Short Code",
"PLACEHOLDER": "Please enter a short code",
"ERROR": "Short Code is required"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
},
"CONTENT": {
"LABEL": "Content",
"PLACEHOLDER": "Please enter a content",
"ERROR": "Content is required"
"LABEL": "Message",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Submit"
},
"API": {
"SUCCESS_MESSAGE": "Canned Response added successfully",
"ERROR_MESSAGE": "Could not create canned response. Please try again later."
"SUCCESS_MESSAGE": "Canned response added successfully.",
"ERROR_MESSAGE": "Could not connect to Woot server. Please try again."
}
},
"EDIT": {
"TITLE": "Edit Canned Response",
"TITLE": "Edit canned response",
"CANCEL_BUTTON_TEXT": "Cancel",
"FORM": {
"SHORT_CODE": {
"LABEL": "Short Code",
"PLACEHOLDER": "Please enter a shortcode",
"ERROR": "Short Code is required"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
},
"CONTENT": {
"LABEL": "Content",
"PLACEHOLDER": "Please enter a content",
"ERROR": "Content is required"
"LABEL": "Message",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Submit"
},
"BUTTON_TEXT": "Edit",
"API": {
"SUCCESS_MESSAGE": "Canned Response updated successfully",
"ERROR_MESSAGE": "Could not update canned response. Please try again later."
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"ERROR_MESSAGE": "Could not connect to Woot server. Please try again."
}
},
"DELETE": {
"BUTTON_TEXT": "Delete",
"API": {
"SUCCESS_MESSAGE": "Canned response deleted successfully",
"ERROR_MESSAGE": "Could not delete canned response. Please try again later."
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"ERROR_MESSAGE": "Could not connect to Woot server. Please try again."
},
"CONFIRM": {
"TITLE": "Confirm Deletion",
"TITLE": "Confirm deletion",
"MESSAGE": "Are you sure to delete ",
"YES": "Yes, Delete ",
"NO": "No, Keep "
"YES": "Yes, delete ",
"NO": "No, keep "
}
}
}
@@ -51,18 +51,30 @@
"ACTIVE": "Last activity"
}
},
"CHAT_SORT_FILTER_ITEMS": {
"latest": {
"TEXT": "Last activity"
"SORT_ORDER_ITEMS": {
"last_activity_at_asc": {
"TEXT": "Last activity: Oldest first"
},
"sort_on_created_at": {
"TEXT": "Created at"
"last_activity_at_desc": {
"TEXT": "Last activity: Newest first"
},
"sort_on_priority": {
"TEXT": "Priority"
"created_at_desc": {
"TEXT": "Created at: Newest first"
},
"sort_on_waiting_since": {
"TEXT": "Pending Response"
"created_at_asc": {
"TEXT": "Created at: Oldest first"
},
"priority_desc": {
"TEXT": "Priority: Highest first"
},
"priority_asc": {
"TEXT": "Priority: Lowest first"
},
"waiting_since_asc": {
"TEXT": "Pending Response: Longest first"
},
"waiting_since_desc": {
"TEXT": "Pending Response: Shortest first"
}
},
"ATTACHMENTS": {
@@ -41,6 +41,9 @@
"SAVE_CONTACT": "Save",
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported.",
"UNSUPPORTED_MESSAGE_FACEBOOK": "This message is unsupported. You can view this message on the Facebook Messenger app.",
"UNSUPPORTED_MESSAGE_INSTAGRAM": "This message is unsupported. You can view this message on the Instagram app.",
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
"NO_RESPONSE": "No response",
@@ -139,6 +142,7 @@
"PRIVATE_NOTE": "Private Note",
"SEND": "Send",
"CREATE": "Add Note",
"INSERT_READ_MORE": "Read more",
"DISMISS_REPLY": "Dismiss reply",
"REPLYING_TO": "Replying to:",
"TIP_FORMAT_ICON": "Show rich text editor",
@@ -92,7 +92,7 @@
},
"NETWORK": {
"NOTIFICATION": {
"TEXT": "Disconnected from Chatwoot"
"OFFLINE": "Offline"
},
"BUTTON": {
"REFRESH": "Refresh"
@@ -3,6 +3,7 @@
"HEADER": {
"FILTER": "Filter by",
"SORT": "Sort by",
"LOCALE": "Locale",
"SETTINGS_BUTTON": "Settings",
"NEW_BUTTON": "New Article",
"DROPDOWN_OPTIONS": {
@@ -15,6 +16,12 @@
"MINE": "My Articles",
"DRAFT": "Draft Articles",
"ARCHIVED": "Archived Articles"
},
"LOCALE_SELECT": {
"TITLE": "Select locale",
"PLACEHOLDER": "Select locale",
"NO_RESULT": "No locale found",
"SEARCH_PLACEHOLDER": "Search locale"
}
},
"EDIT_HEADER": {
@@ -76,6 +83,9 @@
},
"ARTICLE_SEARCH_RESULT": {
"UNCATEGORIZED": "Uncategorized",
"SEARCH_RESULTS": "Search results for %{query}",
"EMPTY_TEXT": "Search for articles to insert into replies.",
"SEARCH_LOADER": "Searching...",
"INSERT_ARTICLE": "Insert",
"NO_RESULT": "No articles found",
"COPY_LINK": "Copy article link to clipboard",
@@ -220,7 +230,10 @@
"LOGO": {
"LABEL": "Logo",
"UPLOAD_BUTTON": "Upload logo",
"HELP_TEXT": "This logo will be displayed on the portal header."
"HELP_TEXT": "This logo will be displayed on the portal header.",
"IMAGE_UPLOAD_SUCCESS": "Logo uploaded successfully",
"IMAGE_UPLOAD_ERROR": "Logo deleted successfully",
"IMAGE_DELETE_ERROR": "Error while deleting logo"
},
"NAME": {
"LABEL": "Name",
@@ -426,6 +439,21 @@
}
}
},
"ARTICLE_SEARCH": {
"TITLE": "Search articles",
"PLACEHOLDER": "Search articles",
"NO_RESULT": "No articles found",
"SEARCHING": "Searching...",
"SEARCH_BUTTON": "Search",
"INSERT_ARTICLE": "Insert link",
"IFRAME_ERROR": "URL is empty or invalid. Unable to display content.",
"OPEN_ARTICLE_SEARCH": "Insert article from Help Center",
"SUCCESS_ARTICLE_INSERTED": "Article inserted successfully",
"PREVIEW_LINK": "Preview article",
"CANCEL": "Close",
"BACK": "Back",
"BACK_RESULTS": "Back to results"
},
"UPGRADE_PAGE": {
"TITLE": "Help Center",
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
@@ -1,18 +1,18 @@
{
"FILTER": {
"TITLE": "تصفية المحادثات",
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية المحادثات.",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "إضافة عامل تصفية",
"FILTER_DELETE_ERROR": "يجب أن يكون لديك عامل تصفية واحد على الأقل للحفظ",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBMIT_BUTTON_LABEL": "تطبيق عامل التصفية",
"UPDATE_BUTTON_LABEL": "Update folder",
"CANCEL_BUTTON_LABEL": "إلغاء",
"CLEAR_BUTTON_LABEL": "مسح عامل التصفية",
"EMPTY_VALUE_ERROR": "القيمة مطلوبة",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"EMPTY_VALUE_ERROR": "القيمة مطلوبة.",
"TOOLTIP_LABEL": "تصفية المحادثات",
"QUERY_DROPDOWN_LABELS": {
"AND": "و",
@@ -36,15 +36,15 @@
},
"ATTRIBUTES": {
"STATUS": "الحالة",
"ASSIGNEE_NAME": "اسم المحال إليه",
"INBOX_NAME": "اسم صندوق الوارد لقناة التواصل",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "اسم صندوق الوارد",
"TEAM_NAME": "اسم الفريق",
"CONVERSATION_IDENTIFIER": "معرف المحادثة",
"CAMPAIGN_NAME": "اسم الحملة",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"LABELS": "الوسوم",
"BROWSER_LANGUAGE": "لغة المتصفح",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "الأولوية",
"COUNTRY_NAME": "اسم الدولة",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "رابط المرجع",
"CUSTOM_ATTRIBUTE_LIST": "القائمة",
"CUSTOM_ATTRIBUTE_TEXT": "النص",
@@ -52,28 +52,28 @@
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع",
"CREATED_AT": "تم إنشاؤها في",
"LAST_ACTIVITY": "آخر نشاط"
"LAST_ACTIVITY": "النشاط الأخير"
},
"GROUPS": {
"STANDARD_FILTERS": "الفلاتر القياسية",
"ADDITIONAL_FILTERS": "فلاتر إضافية",
"CUSTOM_ATTRIBUTES": "سمات مخصصة"
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "هل تريد حفظ هذا الفلتر؟",
"LABEL": "تسمية هذا الفلتر",
"PLACEHOLDER": "أدخل اسم لهذا الفلتر",
"ERROR_MESSAGE": "الاسم مطلوب",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "الاسم مطلوب.",
"SAVE_BUTTON": "حفظ الفلتر",
"CANCEL_BUTTON": "إلغاء",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح.",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح.",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة."
}
},
"EDIT": {
@@ -85,17 +85,17 @@
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من حذف الفلتر ",
"YES": "نعم، احذف",
"YES": "Yes, delete",
"NO": "لا، احتفظ به"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "تم حذف طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف المجلد"
"SUCCESS_MESSAGE": "تم حذف طريقة عرض مخصصة بنجاح.",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف المجلد."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "تم حذف العرض المخصص بنجاح",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف طريقة عرض مخصصة"
"SUCCESS_MESSAGE": "تم حذف العرض المخصص بنجاح.",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف طريقة عرض مخصصة."
}
}
}
@@ -1,73 +1,73 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading Editor...",
"HEADER_BTN_TXT": "Add Bot Configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
"NAME": {
"LABEL": "Bot Name",
"PLACEHOLDER": "Give your bot a name",
"ERROR": "Bot name is required"
"LABEL": "Bot name",
"PLACEHOLDER": "Name your bot.",
"ERROR": "Bot name is required."
},
"DESCRIPTION": {
"LABEL": "Bot Description",
"LABEL": "Bot description",
"PLACEHOLDER": "What does this bot do?"
},
"BOT_CONFIG": {
"ERROR": "Please enter your CSML bot configuration above",
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
"ERROR": "Please enter your CSML bot configuration above.",
"API_ERROR": "Your CSML configuration is invalid. Please fix it and try again."
},
"SUBMIT": "Validate and save"
},
"BOT_CONFIGURATION": {
"TITLE": "Select an agent bot",
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
"DESC": "Assign an Agent Bot to your inbox. They can handle initial conversations and transfer them to a live agent when necessary.",
"SUBMIT": "تحديث",
"DISCONNECT": "Disconnect Bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
"SELECT_PLACEHOLDER": "Select Bot"
"DISCONNECT": "Disconnect bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot.",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot.",
"ERROR_MESSAGE": "Could not update the agent bot. Please try again.",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot. Please try again.",
"SELECT_PLACEHOLDER": "Select bot"
},
"ADD": {
"TITLE": "Configure new bot",
"CANCEL_BUTTON_TEXT": "إلغاء",
"API": {
"SUCCESS_MESSAGE": "Bot added successfully",
"ERROR_MESSAGE": "Could not add bot, Please try again later"
"SUCCESS_MESSAGE": "Bot added successfully.",
"ERROR_MESSAGE": "Could not add bot. Please try again later."
}
},
"LIST": {
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
"LOADING": "Fetching Bots...",
"TYPE": "Bot Type"
"404": "No bots found. You can create a bot by clicking the 'Configure new bot' button ↗",
"LOADING": "Fetching bots...",
"TYPE": "Bot type"
},
"DELETE": {
"BUTTON_TEXT": "حذف",
"TITLE": "Delete Bot",
"TITLE": "Delete bot",
"SUBMIT": "حذف",
"CANCEL_BUTTON_TEXT": "إلغاء",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible.",
"API": {
"SUCCESS_MESSAGE": "Bot deleted successfully",
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
"SUCCESS_MESSAGE": "Bot deleted successfully.",
"ERROR_MESSAGE": "Could not delete bot. Please try again."
}
},
"EDIT": {
"BUTTON_TEXT": "تعديل",
"LOADING": "Fetching Bots...",
"TITLE": "Edit Bot",
"LOADING": "Fetching bots...",
"TITLE": "Edit bot",
"CANCEL_BUTTON_TEXT": "إلغاء",
"API": {
"SUCCESS_MESSAGE": "Bot updated successfully",
"ERROR_MESSAGE": "Could not update bot, Please try again later"
"SUCCESS_MESSAGE": "Bot updated successfully.",
"ERROR_MESSAGE": "Could not update bot. Please try again."
}
},
"TYPES": {
"WEBHOOK": "Webhook Bot",
"CSML": "CSML Bot"
"WEBHOOK": "Webhook bot",
"CSML": "CSML bot"
}
}
}
@@ -7,34 +7,34 @@
"GO_BACK_LABEL": "العودة للخلف",
"ASSIGN_LABEL": "تكليف",
"YES": "نعم",
"ASSIGN_AGENT_TOOLTIP": "إسناد وكيل",
"ASSIGN_AGENT_TOOLTIP": "تعيين وكيل",
"ASSIGN_TEAM_TOOLTIP": "تعيين فريق",
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح",
"ASSIGN_FAILED": "فشل في تعيين المحادثات، الرجاء المحاولة مرة أخرى",
"RESOLVE_SUCCESFUL": "تم تسوية المحادثات بنجاح",
"RESOLVE_FAILED": "فشل في حل المحادثات، يرجى المحاولة مرة أخرى",
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح.",
"ASSIGN_FAILED": "Failed to assign conversations. Please try again.",
"RESOLVE_SUCCESFUL": "تم تسوية المحادثات بنجاح.",
"RESOLVE_FAILED": "Failed to resolve conversations. Please try again.",
"ALL_CONVERSATIONS_SELECTED_ALERT": "المحادثات المرئية في هذه الصفحة هي المحددة فقط.",
"AGENT_LIST_LOADING": "تحميل الوكلاء",
"AGENT_LIST_LOADING": "جاري تحميل الوكلاء",
"UPDATE": {
"CHANGE_STATUS": "تغيير الحالة",
"SNOOZE_UNTIL_NEXT_REPLY": "غفوة حتى الرد القادم",
"SNOOZE_UNTIL_NEXT_REPLY": "غفوة حتى الرد القادم.",
"UPDATE_SUCCESFUL": "تم تحديث حالة المحادثة بنجاح.",
"UPDATE_FAILED": "فشل تحديث المحادثات، الرجاء المحاولة مرة أخرى"
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
"LABELS": {
"ASSIGN_LABELS": "تعيين التسميات",
"ASSIGN_LABELS": "Assign labels",
"NO_LABELS_FOUND": "لم يتم العثور على تسميات لـ",
"ASSIGN_SELECTED_LABELS": "تعيين التسميات المحددة",
"ASSIGN_SUCCESFUL": "تم تعيين التسميات بنجاح",
"ASSIGN_FAILED": "فشل في تعيين التسميات ، الرجاء المحاولة مرة أخرى"
"ASSIGN_SUCCESFUL": "تم تعيين التسميات بنجاح.",
"ASSIGN_FAILED": "Failed to assign labels. Please try again."
},
"TEAMS": {
"TEAM_SELECT_LABEL": "اختيار فريق",
"NONE": "لا شيء",
"NO_TEAMS_AVAILABLE": "لا توجد فرق مضافة إلى هذا الحساب حتى الآن.",
"ASSIGN_SELECTED_TEAMS": "تعيين فريق محدد",
"ASSIGN_SUCCESFUL": "تم تعيين الفرق بنجاح",
"ASSIGN_FAILED": "فشل تعيين الفريق، الرجاء المحاولة مرة أخرى"
"ASSIGN_SELECTED_TEAMS": "تعيين فريق محدد.",
"ASSIGN_SUCCESFUL": "تم تعيين الفرق بنجاح.",
"ASSIGN_FAILED": "Failed to assign team. Please try again."
}
}
}
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "الردود السريعة",
"HEADER_BTN_TXT": "إضافة رد جاهز",
"LOADING": "جاري جلب الردود الجاهزة",
"SEARCH_404": "لا توجد عناصر مطابقة لهذا الاستعلام",
"SIDEBAR_TXT": "<p><b>الردود الجاهزة</b> </p><p> الردود الجاهزة هي عبارة عن قوالب يتم إعدادها مسبقاً لاستخدامها في كتابة الرسائل والردود للإجابة على اكثر الاسئلة شيوعاً. </p><p> لإضافة قالب جديد، فقط انقر على <b>إضافة رد جاهز</b>. يمكنك أيضاً تعديل أو حذف الردود الجاهزة الأخرى بالنقر على زر التحرير أو الحذف </p><p> يتم تسهيل استخدام الردود المسبقة بواسطة <b>رموز مختصرة</b> لتسريع البحث والوصول إليها. يمكن للموظفين الوصول إلى قوالب الردود الجاهزة أثناء الدردشة عن طريق الضغط على زر <b>'/'</b> ومن ثم الاختصار الخاص بالقالب المراد إدخاله في مربع النص. </p>",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"SEARCH_404": "لا توجد عناصر مطابقة لهذا الاستعلام.",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"LIST": {
"404": "لا توجد ردود جاهزة متوفرة في هذا الحساب.",
"TITLE": "إدارة الردود الجاهزة",
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات.",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TABLE_HEADER": [
"الرمز المختصر",
"Short code",
"المحتوى",
"الإجراءات"
]
},
"ADD": {
"TITLE": "إضافة رد جاهز",
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات.",
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"CANCEL_BUTTON_TEXT": "إلغاء",
"FORM": {
"SHORT_CODE": {
"LABEL": "الرمز المختصر",
"PLACEHOLDER": "الرجاء إدخال رمز مختصر",
"ERROR": "الرمز المختصر مطلوب"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
},
"CONTENT": {
"LABEL": "المحتوى",
"PLACEHOLDER": "الرجاء إدخال محتوى",
"ERROR": "المحتوى مطلوب"
"LABEL": "رسالة",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "إرسال"
},
"API": {
"SUCCESS_MESSAGE": "تم إضافة قالب الرد الجاهز بنجاح",
"SUCCESS_MESSAGE": "Canned response added successfully.",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
}
},
"EDIT": {
"TITLE": "تعديل قالب الرد الجاهز",
"TITLE": "Edit canned response",
"CANCEL_BUTTON_TEXT": "إلغاء",
"FORM": {
"SHORT_CODE": {
"LABEL": "الرمز المختصر",
"PLACEHOLDER": "الرجاء إدخال رمز مختصر",
"ERROR": "الرمز المختصر مطلوب"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
},
"CONTENT": {
"LABEL": "المحتوى",
"PLACEHOLDER": "الرجاء إدخال محتوى",
"ERROR": "المحتوى مطلوب"
"LABEL": "رسالة",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "الرسالة مطلوبة."
},
"SUBMIT": "إرسال"
},
"BUTTON_TEXT": "تعديل",
"API": {
"SUCCESS_MESSAGE": "تم تحديث قالب الرد الجاهز بنجاح",
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
}
},
"DELETE": {
"BUTTON_TEXT": "حذف",
"API": {
"SUCCESS_MESSAGE": "تم حذف قالب الرد الجاهز بنجاح",
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
},
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من الحذف ",
"YES": "نعم، احذف ",
"NO": "لا، احتفظ "
"YES": "Yes, delete ",
"NO": "No, keep "
}
}
}
@@ -51,18 +51,30 @@
"ACTIVE": "النشاط الأخير"
}
},
"CHAT_SORT_FILTER_ITEMS": {
"latest": {
"TEXT": "النشاط الأخير"
"SORT_ORDER_ITEMS": {
"last_activity_at_asc": {
"TEXT": "Last activity: Oldest first"
},
"sort_on_created_at": {
"TEXT": "تم إنشاؤها في"
"last_activity_at_desc": {
"TEXT": "Last activity: Newest first"
},
"sort_on_priority": {
"TEXT": "الأولوية"
"created_at_desc": {
"TEXT": "Created at: Newest first"
},
"sort_on_waiting_since": {
"TEXT": "Pending Response"
"created_at_asc": {
"TEXT": "Created at: Oldest first"
},
"priority_desc": {
"TEXT": "Priority: Highest first"
},
"priority_asc": {
"TEXT": "Priority: Lowest first"
},
"waiting_since_asc": {
"TEXT": "Pending Response: Longest first"
},
"waiting_since_desc": {
"TEXT": "Pending Response: Shortest first"
}
},
"ATTACHMENTS": {
@@ -41,6 +41,9 @@
"SAVE_CONTACT": "حفظ",
"UPLOADING_ATTACHMENTS": "جاري تحميل المرفقات...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported.",
"UNSUPPORTED_MESSAGE_FACEBOOK": "This message is unsupported. You can view this message on the Facebook Messenger app.",
"UNSUPPORTED_MESSAGE_INSTAGRAM": "This message is unsupported. You can view this message on the Instagram app.",
"SUCCESS_DELETE_MESSAGE": "تم حذف الرسالة بنجاح",
"FAIL_DELETE_MESSSAGE": "تعذر حذف الرسالة! حاول مرة أخرى",
"NO_RESPONSE": "لا توجد استجابة",
@@ -139,6 +142,7 @@
"PRIVATE_NOTE": "إضافة ملاحظة خاصة",
"SEND": "إرسال",
"CREATE": "إضافة ملاحظة",
"INSERT_READ_MORE": "Read more",
"DISMISS_REPLY": "Dismiss reply",
"REPLYING_TO": "Replying to:",
"TIP_FORMAT_ICON": "عرض محرر النصوص",
@@ -92,7 +92,7 @@
},
"NETWORK": {
"NOTIFICATION": {
"TEXT": "قطع الاتصال من Chatwoot"
"OFFLINE": "غير متصل"
},
"BUTTON": {
"REFRESH": "تحديث"
@@ -3,6 +3,7 @@
"HEADER": {
"FILTER": "تصفية حسب",
"SORT": "ترتيب حسب",
"LOCALE": "اللغة",
"SETTINGS_BUTTON": "الإعدادات",
"NEW_BUTTON": "مقالة جديدة",
"DROPDOWN_OPTIONS": {
@@ -15,6 +16,12 @@
"MINE": "مقالاتي",
"DRAFT": "مقالات مسودة",
"ARCHIVED": "المقالات المؤرشفة"
},
"LOCALE_SELECT": {
"TITLE": "Select locale",
"PLACEHOLDER": "Select locale",
"NO_RESULT": "No locale found",
"SEARCH_PLACEHOLDER": "Search locale"
}
},
"EDIT_HEADER": {
@@ -76,6 +83,9 @@
},
"ARTICLE_SEARCH_RESULT": {
"UNCATEGORIZED": "Uncategorized",
"SEARCH_RESULTS": "Search results for %{query}",
"EMPTY_TEXT": "Search for articles to insert into replies.",
"SEARCH_LOADER": "يبحث...",
"INSERT_ARTICLE": "Insert",
"NO_RESULT": "No articles found",
"COPY_LINK": "Copy article link to clipboard",
@@ -220,7 +230,10 @@
"LOGO": {
"LABEL": "Logo",
"UPLOAD_BUTTON": "Upload logo",
"HELP_TEXT": "This logo will be displayed on the portal header."
"HELP_TEXT": "This logo will be displayed on the portal header.",
"IMAGE_UPLOAD_SUCCESS": "Logo uploaded successfully",
"IMAGE_UPLOAD_ERROR": "Logo deleted successfully",
"IMAGE_DELETE_ERROR": "Error while deleting logo"
},
"NAME": {
"LABEL": "الاسم",
@@ -426,6 +439,21 @@
}
}
},
"ARTICLE_SEARCH": {
"TITLE": "Search articles",
"PLACEHOLDER": "Search articles",
"NO_RESULT": "No articles found",
"SEARCHING": "يبحث...",
"SEARCH_BUTTON": "بحث",
"INSERT_ARTICLE": "Insert link",
"IFRAME_ERROR": "URL is empty or invalid. Unable to display content.",
"OPEN_ARTICLE_SEARCH": "Insert article from Help Center",
"SUCCESS_ARTICLE_INSERTED": "Article inserted successfully",
"PREVIEW_LINK": "Preview article",
"CANCEL": "أغلق",
"BACK": "العودة",
"BACK_RESULTS": "Back to results"
},
"UPGRADE_PAGE": {
"TITLE": "Help Center",
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
@@ -1,18 +1,18 @@
{
"FILTER": {
"TITLE": "Филтрирай разговорите",
"SUBTITLE": "Добавете филтри по-долу и натиснете „Прилагане на филтри“, за да филтрирате разговорите.",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "Добавяне на филтър",
"FILTER_DELETE_ERROR": "За да запазите, трябва да имате поне един филтър",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBMIT_BUTTON_LABEL": "Прилагане на филтри",
"UPDATE_BUTTON_LABEL": "Update folder",
"CANCEL_BUTTON_LABEL": "Отмени",
"CLEAR_BUTTON_LABEL": "Изчисти филтрите",
"EMPTY_VALUE_ERROR": "Изисква се стойност",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"EMPTY_VALUE_ERROR": "Изисква се стойност.",
"TOOLTIP_LABEL": "Филтрирай разговорите",
"QUERY_DROPDOWN_LABELS": {
"AND": "И",
@@ -36,44 +36,44 @@
},
"ATTRIBUTES": {
"STATUS": "Статус",
"ASSIGNEE_NAME": "Име на правоприемника",
"INBOX_NAME": "Име на входящата кутия",
"TEAM_NAME": "Име на екипа",
"CONVERSATION_IDENTIFIER": "Идентивикатор на разговор",
"CAMPAIGN_NAME": "Име на кампания",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "Inbox name",
"TEAM_NAME": "Team name",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"LABELS": "Етикети",
"BROWSER_LANGUAGE": "Език на браузъра",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "Priority",
"COUNTRY_NAME": "Име на държавата",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "Референтна връзка",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Последна активност"
"CREATED_AT": "Създаден в",
"LAST_ACTIVITY": "Last activity"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути"
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "Name is required.",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Отмени",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
"SUCCESS_MESSAGE": "Folder created successfully.",
"ERROR_MESSAGE": "Error while creating folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
"SUCCESS_MESSAGE": "Segment created successfully.",
"ERROR_MESSAGE": "Error while creating segment."
}
},
"EDIT": {
@@ -85,17 +85,17 @@
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Да, изтрий",
"NO": "No, Keep it"
"YES": "Yes, delete",
"NO": "No, keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
"SUCCESS_MESSAGE": "Folder deleted successfully.",
"ERROR_MESSAGE": "Error while deleting folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
"SUCCESS_MESSAGE": "Segment deleted successfully.",
"ERROR_MESSAGE": "Error while deleting segment."
}
}
}
@@ -1,73 +1,73 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading Editor...",
"HEADER_BTN_TXT": "Add Bot Configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
"NAME": {
"LABEL": "Bot Name",
"PLACEHOLDER": "Give your bot a name",
"ERROR": "Bot name is required"
"LABEL": "Bot name",
"PLACEHOLDER": "Name your bot.",
"ERROR": "Bot name is required."
},
"DESCRIPTION": {
"LABEL": "Bot Description",
"LABEL": "Bot description",
"PLACEHOLDER": "What does this bot do?"
},
"BOT_CONFIG": {
"ERROR": "Please enter your CSML bot configuration above",
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
"ERROR": "Please enter your CSML bot configuration above.",
"API_ERROR": "Your CSML configuration is invalid. Please fix it and try again."
},
"SUBMIT": "Validate and save"
},
"BOT_CONFIGURATION": {
"TITLE": "Select an agent bot",
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
"DESC": "Assign an Agent Bot to your inbox. They can handle initial conversations and transfer them to a live agent when necessary.",
"SUBMIT": "Обновяване",
"DISCONNECT": "Disconnect Bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
"SELECT_PLACEHOLDER": "Select Bot"
"DISCONNECT": "Disconnect bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot.",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot.",
"ERROR_MESSAGE": "Could not update the agent bot. Please try again.",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot. Please try again.",
"SELECT_PLACEHOLDER": "Select bot"
},
"ADD": {
"TITLE": "Configure new bot",
"CANCEL_BUTTON_TEXT": "Отмени",
"API": {
"SUCCESS_MESSAGE": "Bot added successfully",
"ERROR_MESSAGE": "Could not add bot, Please try again later"
"SUCCESS_MESSAGE": "Bot added successfully.",
"ERROR_MESSAGE": "Could not add bot. Please try again later."
}
},
"LIST": {
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
"LOADING": "Fetching Bots...",
"TYPE": "Bot Type"
"404": "No bots found. You can create a bot by clicking the 'Configure new bot' button ↗",
"LOADING": "Fetching bots...",
"TYPE": "Bot type"
},
"DELETE": {
"BUTTON_TEXT": "Изтрий",
"TITLE": "Delete Bot",
"TITLE": "Delete bot",
"SUBMIT": "Изтрий",
"CANCEL_BUTTON_TEXT": "Отмени",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible.",
"API": {
"SUCCESS_MESSAGE": "Bot deleted successfully",
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
"SUCCESS_MESSAGE": "Bot deleted successfully.",
"ERROR_MESSAGE": "Could not delete bot. Please try again."
}
},
"EDIT": {
"BUTTON_TEXT": "Редактирай",
"LOADING": "Fetching Bots...",
"TITLE": "Edit Bot",
"LOADING": "Fetching bots...",
"TITLE": "Edit bot",
"CANCEL_BUTTON_TEXT": "Отмени",
"API": {
"SUCCESS_MESSAGE": "Bot updated successfully",
"ERROR_MESSAGE": "Could not update bot, Please try again later"
"SUCCESS_MESSAGE": "Bot updated successfully.",
"ERROR_MESSAGE": "Could not update bot. Please try again."
}
},
"TYPES": {
"WEBHOOK": "Webhook Bot",
"CSML": "CSML Bot"
"WEBHOOK": "Webhook bot",
"CSML": "CSML bot"
}
}
}
@@ -1,40 +1,40 @@
{
"BULK_ACTION": {
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
"AGENT_SELECT_LABEL": "Select Agent",
"AGENT_SELECT_LABEL": "Изберете агент",
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
"GO_BACK_LABEL": "Go back",
"ASSIGN_LABEL": "Assign",
"YES": "Yes",
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
"ASSIGN_AGENT_TOOLTIP": "Assign agent",
"ASSIGN_TEAM_TOOLTIP": "Assign team",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully",
"RESOLVE_FAILED": "Failed to resolve conversations, please try again",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully.",
"ASSIGN_FAILED": "Failed to assign conversations. Please try again.",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully.",
"RESOLVE_FAILED": "Failed to resolve conversations. Please try again.",
"ALL_CONVERSATIONS_SELECTED_ALERT": "Conversations visible on this page are only selected.",
"AGENT_LIST_LOADING": "Loading Agents",
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations, please try again"
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
"LABELS": {
"ASSIGN_LABELS": "Assign Labels",
"ASSIGN_LABELS": "Assign labels",
"NO_LABELS_FOUND": "No labels found for",
"ASSIGN_SELECTED_LABELS": "Assign selected labels",
"ASSIGN_SUCCESFUL": "Labels assigned successfully",
"ASSIGN_FAILED": "Failed to assign labels, please try again"
"ASSIGN_SUCCESFUL": "Labels assigned successfully.",
"ASSIGN_FAILED": "Failed to assign labels. Please try again."
},
"TEAMS": {
"TEAM_SELECT_LABEL": "Select Team",
"TEAM_SELECT_LABEL": "Изберете екип",
"NONE": "Нито един",
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
"ASSIGN_FAILED": "Failed to assign team, please try again"
"ASSIGN_SELECTED_TEAMS": "Assign selected team.",
"ASSIGN_SUCCESFUL": "Teams assiged successfully.",
"ASSIGN_FAILED": "Failed to assign team. Please try again."
}
}
}
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "Готови отговори",
"HEADER_BTN_TXT": "Добавяне на готов отговор",
"LOADING": "Извличане на готови отговори",
"SEARCH_404": "Няма резултати отговарящи на тази заявка",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"SEARCH_404": "Няма резултати отговарящи на тази заявка.",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"LIST": {
"404": "Няма налични готови отговори в този акаунт.",
"TITLE": "Управлявайте готовите отговори",
"DESC": "Готовите отговори са предварително дефинирани шаблони за отговор, които могат да се изпращат бързо в чата.",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TABLE_HEADER": [
"Кратък код",
"Short code",
"Съдържание",
"Действия"
]
},
"ADD": {
"TITLE": "Добавяне на готов отговор",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"CANCEL_BUTTON_TEXT": "Отмени",
"FORM": {
"SHORT_CODE": {
"LABEL": "Кратък код",
"PLACEHOLDER": "Please enter a short code",
"ERROR": "Краткия код е задължителен"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
},
"CONTENT": {
"LABEL": "Съдържание",
"PLACEHOLDER": "Моля, въведете съдържание",
"ERROR": "Съдържанието е задължително"
"LABEL": "Съобщение",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Изпращане"
},
"API": {
"SUCCESS_MESSAGE": "Готовия отговор е добавен успешно",
"SUCCESS_MESSAGE": "Canned response added successfully.",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
}
},
"EDIT": {
"TITLE": "Промени готовия отговор",
"TITLE": "Edit canned response",
"CANCEL_BUTTON_TEXT": "Отмени",
"FORM": {
"SHORT_CODE": {
"LABEL": "Кратък код",
"PLACEHOLDER": "Моля, въведете кратък код",
"ERROR": "Краткия код е задължителен"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
},
"CONTENT": {
"LABEL": "Съдържание",
"PLACEHOLDER": "Моля, въведете съдържание",
"ERROR": "Съдържанието е задължително"
"LABEL": "Съобщение",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Изпращане"
},
"BUTTON_TEXT": "Редактирай",
"API": {
"SUCCESS_MESSAGE": "Готовия отговор е обновен успешно",
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
}
},
"DELETE": {
"BUTTON_TEXT": "Изтрий",
"API": {
"SUCCESS_MESSAGE": "Готовия отговор е изтрит успешно",
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
},
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Сигурни ли сте за изтриването ",
"YES": "Да, изтрий ",
"NO": "Не, запази "
"YES": "Yes, delete ",
"NO": "No, keep "
}
}
}
@@ -51,18 +51,30 @@
"ACTIVE": "Last activity"
}
},
"CHAT_SORT_FILTER_ITEMS": {
"latest": {
"TEXT": "Last activity"
"SORT_ORDER_ITEMS": {
"last_activity_at_asc": {
"TEXT": "Last activity: Oldest first"
},
"sort_on_created_at": {
"TEXT": "Създаден в"
"last_activity_at_desc": {
"TEXT": "Last activity: Newest first"
},
"sort_on_priority": {
"TEXT": "Priority"
"created_at_desc": {
"TEXT": "Created at: Newest first"
},
"sort_on_waiting_since": {
"TEXT": "Pending Response"
"created_at_asc": {
"TEXT": "Created at: Oldest first"
},
"priority_desc": {
"TEXT": "Priority: Highest first"
},
"priority_asc": {
"TEXT": "Priority: Lowest first"
},
"waiting_since_asc": {
"TEXT": "Pending Response: Longest first"
},
"waiting_since_desc": {
"TEXT": "Pending Response: Shortest first"
}
},
"ATTACHMENTS": {
@@ -41,6 +41,9 @@
"SAVE_CONTACT": "Save",
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported.",
"UNSUPPORTED_MESSAGE_FACEBOOK": "This message is unsupported. You can view this message on the Facebook Messenger app.",
"UNSUPPORTED_MESSAGE_INSTAGRAM": "This message is unsupported. You can view this message on the Instagram app.",
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
"NO_RESPONSE": "No response",
@@ -139,6 +142,7 @@
"PRIVATE_NOTE": "Private Note",
"SEND": "Send",
"CREATE": "Add Note",
"INSERT_READ_MORE": "Read more",
"DISMISS_REPLY": "Dismiss reply",
"REPLYING_TO": "Replying to:",
"TIP_FORMAT_ICON": "Show rich text editor",
@@ -92,7 +92,7 @@
},
"NETWORK": {
"NOTIFICATION": {
"TEXT": "Disconnected from Chatwoot"
"OFFLINE": "Offline"
},
"BUTTON": {
"REFRESH": "Refresh"
@@ -3,6 +3,7 @@
"HEADER": {
"FILTER": "Filter by",
"SORT": "Sort by",
"LOCALE": "Locale",
"SETTINGS_BUTTON": "Settings",
"NEW_BUTTON": "New Article",
"DROPDOWN_OPTIONS": {
@@ -15,6 +16,12 @@
"MINE": "My Articles",
"DRAFT": "Draft Articles",
"ARCHIVED": "Archived Articles"
},
"LOCALE_SELECT": {
"TITLE": "Select locale",
"PLACEHOLDER": "Select locale",
"NO_RESULT": "No locale found",
"SEARCH_PLACEHOLDER": "Search locale"
}
},
"EDIT_HEADER": {
@@ -76,6 +83,9 @@
},
"ARTICLE_SEARCH_RESULT": {
"UNCATEGORIZED": "Uncategorized",
"SEARCH_RESULTS": "Search results for %{query}",
"EMPTY_TEXT": "Search for articles to insert into replies.",
"SEARCH_LOADER": "Searching...",
"INSERT_ARTICLE": "Insert",
"NO_RESULT": "No articles found",
"COPY_LINK": "Copy article link to clipboard",
@@ -220,7 +230,10 @@
"LOGO": {
"LABEL": "Logo",
"UPLOAD_BUTTON": "Upload logo",
"HELP_TEXT": "This logo will be displayed on the portal header."
"HELP_TEXT": "This logo will be displayed on the portal header.",
"IMAGE_UPLOAD_SUCCESS": "Logo uploaded successfully",
"IMAGE_UPLOAD_ERROR": "Logo deleted successfully",
"IMAGE_DELETE_ERROR": "Error while deleting logo"
},
"NAME": {
"LABEL": "Име",
@@ -426,6 +439,21 @@
}
}
},
"ARTICLE_SEARCH": {
"TITLE": "Search articles",
"PLACEHOLDER": "Search articles",
"NO_RESULT": "No articles found",
"SEARCHING": "Searching...",
"SEARCH_BUTTON": "Търсене",
"INSERT_ARTICLE": "Insert link",
"IFRAME_ERROR": "URL is empty or invalid. Unable to display content.",
"OPEN_ARTICLE_SEARCH": "Insert article from Help Center",
"SUCCESS_ARTICLE_INSERTED": "Article inserted successfully",
"PREVIEW_LINK": "Preview article",
"CANCEL": "Затвори",
"BACK": "Back",
"BACK_RESULTS": "Back to results"
},
"UPGRADE_PAGE": {
"TITLE": "Help Center",
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
@@ -1,18 +1,18 @@
{
"FILTER": {
"TITLE": "Filtre de converses",
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "Afegeix filtre",
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBMIT_BUTTON_LABEL": "Aplicar filtres",
"UPDATE_BUTTON_LABEL": "Update folder",
"CANCEL_BUTTON_LABEL": "Cancel·la",
"CLEAR_BUTTON_LABEL": "Clear Filters",
"EMPTY_VALUE_ERROR": "El valor és necessari",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"EMPTY_VALUE_ERROR": "El valor és necessari.",
"TOOLTIP_LABEL": "Filtre de converses",
"QUERY_DROPDOWN_LABELS": {
"AND": "I",
@@ -36,44 +36,44 @@
},
"ATTRIBUTES": {
"STATUS": "Estat",
"ASSIGNEE_NAME": "Assignee Name",
"INBOX_NAME": "Nom de la safata d'entrada",
"TEAM_NAME": "Nom de l'equip",
"CONVERSATION_IDENTIFIER": "Identificador de la conversa",
"CAMPAIGN_NAME": "Campaign Name",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "Inbox name",
"TEAM_NAME": "Team name",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"LABELS": "Etiquetes",
"BROWSER_LANGUAGE": "Browser Language",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "Priority",
"COUNTRY_NAME": "Nom del país",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Last Activity"
"CREATED_AT": "Created at",
"LAST_ACTIVITY": "Last activity"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "Name is required.",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Cancel·la",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
"SUCCESS_MESSAGE": "Folder created successfully.",
"ERROR_MESSAGE": "Error while creating folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
"SUCCESS_MESSAGE": "Segment created successfully.",
"ERROR_MESSAGE": "Error while creating segment."
}
},
"EDIT": {
@@ -83,19 +83,19 @@
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"TITLE": "Confirm deletion",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Si, esborra",
"NO": "No, manten-la"
"YES": "Yes, delete",
"NO": "No, keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
"SUCCESS_MESSAGE": "Folder deleted successfully.",
"ERROR_MESSAGE": "Error while deleting folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
"SUCCESS_MESSAGE": "Segment deleted successfully.",
"ERROR_MESSAGE": "Error while deleting segment."
}
}
}
@@ -1,73 +1,73 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading Editor...",
"HEADER_BTN_TXT": "Add Bot Configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
"NAME": {
"LABEL": "Bot Name",
"PLACEHOLDER": "Give your bot a name",
"ERROR": "Bot name is required"
"LABEL": "Bot name",
"PLACEHOLDER": "Name your bot.",
"ERROR": "Bot name is required."
},
"DESCRIPTION": {
"LABEL": "Bot Description",
"LABEL": "Bot description",
"PLACEHOLDER": "What does this bot do?"
},
"BOT_CONFIG": {
"ERROR": "Please enter your CSML bot configuration above",
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
"ERROR": "Please enter your CSML bot configuration above.",
"API_ERROR": "Your CSML configuration is invalid. Please fix it and try again."
},
"SUBMIT": "Validate and save"
},
"BOT_CONFIGURATION": {
"TITLE": "Select an agent bot",
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
"DESC": "Assign an Agent Bot to your inbox. They can handle initial conversations and transfer them to a live agent when necessary.",
"SUBMIT": "Actualitza",
"DISCONNECT": "Disconnect Bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
"SELECT_PLACEHOLDER": "Select Bot"
"DISCONNECT": "Disconnect bot",
"SUCCESS_MESSAGE": "Successfully updated the agent bot.",
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot.",
"ERROR_MESSAGE": "Could not update the agent bot. Please try again.",
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot. Please try again.",
"SELECT_PLACEHOLDER": "Select bot"
},
"ADD": {
"TITLE": "Configure new bot",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"API": {
"SUCCESS_MESSAGE": "Bot added successfully",
"ERROR_MESSAGE": "Could not add bot, Please try again later"
"SUCCESS_MESSAGE": "Bot added successfully.",
"ERROR_MESSAGE": "Could not add bot. Please try again later."
}
},
"LIST": {
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
"LOADING": "Fetching Bots...",
"TYPE": "Bot Type"
"404": "No bots found. You can create a bot by clicking the 'Configure new bot' button ↗",
"LOADING": "Fetching bots...",
"TYPE": "Bot type"
},
"DELETE": {
"BUTTON_TEXT": "Esborrar",
"TITLE": "Delete Bot",
"TITLE": "Delete bot",
"SUBMIT": "Esborrar",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible.",
"API": {
"SUCCESS_MESSAGE": "Bot deleted successfully",
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
"SUCCESS_MESSAGE": "Bot deleted successfully.",
"ERROR_MESSAGE": "Could not delete bot. Please try again."
}
},
"EDIT": {
"BUTTON_TEXT": "Edita",
"LOADING": "Fetching Bots...",
"TITLE": "Edit Bot",
"LOADING": "Fetching bots...",
"TITLE": "Edit bot",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"API": {
"SUCCESS_MESSAGE": "Bot updated successfully",
"ERROR_MESSAGE": "Could not update bot, Please try again later"
"SUCCESS_MESSAGE": "Bot updated successfully.",
"ERROR_MESSAGE": "Could not update bot. Please try again."
}
},
"TYPES": {
"WEBHOOK": "Webhook Bot",
"CSML": "CSML Bot"
"WEBHOOK": "Webhook bot",
"CSML": "CSML bot"
}
}
}
@@ -7,34 +7,34 @@
"GO_BACK_LABEL": "Go back",
"ASSIGN_LABEL": "Assignar",
"YES": "Si",
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
"ASSIGN_AGENT_TOOLTIP": "Assign agent",
"ASSIGN_TEAM_TOOLTIP": "Assign team",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully",
"RESOLVE_FAILED": "Failed to resolve conversations, please try again",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully.",
"ASSIGN_FAILED": "Failed to assign conversations. Please try again.",
"RESOLVE_SUCCESFUL": "Conversations resolved successfully.",
"RESOLVE_FAILED": "Failed to resolve conversations. Please try again.",
"ALL_CONVERSATIONS_SELECTED_ALERT": "Conversations visible on this page are only selected.",
"AGENT_LIST_LOADING": "Loading Agents",
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations, please try again"
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
"LABELS": {
"ASSIGN_LABELS": "Assign Labels",
"ASSIGN_LABELS": "Assign labels",
"NO_LABELS_FOUND": "No labels found for",
"ASSIGN_SELECTED_LABELS": "Assign selected labels",
"ASSIGN_SUCCESFUL": "Labels assigned successfully",
"ASSIGN_FAILED": "Failed to assign labels, please try again"
"ASSIGN_SUCCESFUL": "Labels assigned successfully.",
"ASSIGN_FAILED": "Failed to assign labels. Please try again."
},
"TEAMS": {
"TEAM_SELECT_LABEL": "Select Team",
"TEAM_SELECT_LABEL": "Selecciona equip",
"NONE": "Ningú",
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
"ASSIGN_FAILED": "Failed to assign team, please try again"
"ASSIGN_SELECTED_TEAMS": "Assign selected team.",
"ASSIGN_SUCCESFUL": "Teams assiged successfully.",
"ASSIGN_FAILED": "Failed to assign team. Please try again."
}
}
}
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "Respostes predeterminades",
"HEADER_BTN_TXT": "Afegeix una resposta predeterminada",
"LOADING": "S'estan recollint les respostes predeterminades",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta.",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"LIST": {
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
"TITLE": "Gestiona les respostes predeterminades",
"DESC": "Les respostes predeterminades són plantilles de resposta predefinides que es poden utilitzar per enviar ràpidament respostes a les converses.",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TABLE_HEADER": [
"Codi curt",
"Short code",
"Contingut",
"Accions"
]
},
"ADD": {
"TITLE": "Afegeix Resposta Predeterminada",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt",
"ERROR": "És necessari el codi curt"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
},
"CONTENT": {
"LABEL": "Contingut",
"PLACEHOLDER": "Introduïu un contingut",
"ERROR": "És necessari un contingut"
"LABEL": "Missatge",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Envia"
},
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada afegida correctament",
"SUCCESS_MESSAGE": "Canned response added successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"EDIT": {
"TITLE": "Edita la resposta predeterminada",
"TITLE": "Edit canned response",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt",
"ERROR": "És necessari el codi curt"
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
},
"CONTENT": {
"LABEL": "Contingut",
"PLACEHOLDER": "Introduïu un contingut",
"ERROR": "És necessari un contingut"
"LABEL": "Missatge",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Envia"
},
"BUTTON_TEXT": "Edita",
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada actualitzada correctament",
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"DELETE": {
"BUTTON_TEXT": "Esborra",
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada eliminada correctament",
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
},
"CONFIRM": {
"TITLE": "Confirma esborrat",
"TITLE": "Confirm deletion",
"MESSAGE": "N'estas segur ",
"YES": "Si, esborra ",
"NO": "No, segueix "
"YES": "Yes, delete ",
"NO": "No, keep "
}
}
}

Some files were not shown because too many files have changed in this diff Show More