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
Sojan 29478880ff Bump version to 3.4.0 2023-12-18 21:22:06 -08:00
Vishnu NarayananandGitHub c9f8cdb51b fix: switch to nodejs ppa for linux installations (#8337)
Nodesource script-based installation is deprecated. Switch to a different ppa for nodejs.

Fixes: https://linear.app/chatwoot/issue/CW-2686/upgrade-warning
Fixes: #8151
2023-12-18 21:18:42 -08:00
Pranav Raj SandGitHub a2ebf92932 fix: Fix the issue "Undefined method `inbox' for Instagram::MessageText" (#8582) 2023-12-19 08:43:43 +05:30
Pranav Raj SandGitHub 00eb5b152a chore(migration): Re-run database migration job for caching. (#8581)
A support request that came to Chatwoot Cloud revealed that the job was timed prematurely. The default timeout for Sidekiq queues was set as 25 minutes in sidekiq.yml file. The cache was not created properly for the accounts with more than 100k conversations.

This change removes the cache logic in the previous migration and creates a new migration with a new job which processes conversations in batch.
2023-12-18 17:29:02 -08:00
Sojan JoseandGitHub 0b2f539ad0 chore: Update self-hosted billing redirect (#8580) 2023-12-18 16:34:47 -08:00
Shivam MishraandGitHub 1ab3966b75 fix: @inbox usage in logs (#8578) 2023-12-18 19:30:41 +05:30
552d967c81 fix: Use ogg format for Telegram audio messages (#8505)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-16 11:55:47 -08:00
Sivin VargheseandGitHub e5c8a68a76 fix: Typing indicator to support dark mode (#8564) 2023-12-15 09:57:13 +05:30
Sivin VargheseandGitHub 15f50635e7 fix: undefined method `available_name' for nil:NilClass (#8563) 2023-12-15 09:41:35 +05:30
LiamandGitHub cf0d6dd7c6 feat: Implement the ability to be able to rotate an image on agent conversation. (#8559) 2023-12-15 09:20:24 +05:30
Sivin VargheseandGitHub fd1813949a fix: Undefined method `name' for nil:NilClass (#8562) 2023-12-15 09:03:16 +05:30
Shivam MishraandGitHub 6c480098f7 chore: log authentication error for FB and Instagram (#8551) 2023-12-14 16:18:23 +05:30
Shivam MishraandGitHub ae649a5b3f refactor: add better logs to Instagram::MessageText (#8538) 2023-12-14 14:03:40 +05:30
34675da6fa feat: Settings screen for custom branding (#8543)
- Ability to configure Chatwoot custom branding from UI when using paid plans

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2023-12-13 13:16:24 -08:00
075b0292b5 feat: Conversation list virtualization (#8540)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-13 17:28:30 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Sivin VargheseShivam Mishra
0e9825f298 chore(deps): bump axios from 0.21.2 to 1.6.0 (#8339)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2023-12-13 16:24:10 +05:30
bf883794a4 fix: accepting external source_id (#8359)
Co-authored-by: Sojan <sojan@pepalo.com>
2023-12-13 16:21:55 +05:30
Sivin VargheseandGitHub 3adaa2d602 fix: Retry message not working if the conversation has an external issue (#8529) 2023-12-13 15:46:10 +05:30
60a312ace5 feat: Advanced conversation sort options (#8532)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-12 19:59:31 -08:00
Pranav Raj SandGitHub 8c9a351c84 feat: Add support for ascending and descending options in sort (#8542) 2023-12-12 17:15:48 -08:00
Pranav Raj SandGitHub d28f512de4 fix: Update the label name to use .strip to avoid unnecessary whitespace characters (#8541)
- The cache stores the labels as label1, label2. Without removing the whitespace, the output of cached_label_list_array would be [label1, [whitespace]label2], which doesn't match with the existing labels. This list is returned with proper label names.
2023-12-12 15:04:29 -08:00
Sojan JoseandGitHub 376de685fb chore: Adds API for agent bot avatar upload (#8533)
Adds API for agent bot avatar upload
- accounts/agent_bot
- platform/agent_bot
2023-12-11 19:02:11 -08:00
Pranav Raj SandGitHub 890515edfd feat(perf): Cache labels on the conversation model (#8527) 2023-12-11 18:27:55 -08:00
Shivam MishraandGitHub 79412ba2c6 feat: disable reply to message in WhatsApp via 360 dialog (#8500) 2023-12-11 16:54:47 +05:30
03ebb6947b chore(synk): Upgrade sentry-rails from 5.13.0 to 5.14.0 (#8420)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-10 22:00:17 -08:00
c53591f049 chore(snyk): Security upgrade sidekiq-cron from 1.11.0 to 1.12.0 (#8523)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-10 21:59:12 -08:00
Muhsin KelothandGitHub db9a32a4c0 feat: Add sticker support in Line channel (#8488) 2023-12-10 20:45:44 -08:00
2ae9bbb24e chore(snyk): Security upgrade factory_bot_rails from 6.2.0 to 6.4.2 (#8371)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-10 20:43:58 -08:00
8dfe193461 feat: Add infinite loader, option for increasing page size (#8525)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-12-10 20:09:17 -08:00
Sivin VargheseandGitHub 27239ae14a feat: Adds API for retry messages in conversation (#8518) 2023-12-11 09:33:39 +05:30
Muhsin KelothandGitHub 80ff5e2d0a chore: Change the RemoveMessageNotifications job implementation. (#8521) 2023-12-11 08:27:30 +05:30
38240a68aa [Snyk] Security upgrade rack-mini-profiler from 3.1.1 to 3.2.0 (#8508)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-12-10 15:02:59 -08:00
Sojan JoseandGitHub 89710b406e chore: Remove missing styles for super admin (#8519)
- Remove missing stylesheet references for super admin
2023-12-08 20:47:57 -08:00
f002870c6a feat: Super admin design improvements (#8517)
- Revamp the super admin design
- Introduce a new settings page for support and billing settings
- Move the access tokens into users, agent bots and platform app show pages


Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-12-08 19:40:35 -08:00
Muhsin KelothandGitHub b1a68759cf feat: Add the support for custom attributes in message variables (#8511) 2023-12-08 14:13:35 -08:00
Muhsin KelothandGitHub bf49a17efa feat: Handle notification.deleted action cable event (#8501) 2023-12-06 16:49:00 +05:30
Muhsin KelothandGitHub 6b0d1d77d3 fix: Email notifications (#8502) 2023-12-06 15:50:23 +05:30
Muhsin KelothandGitHub db33b7d1dc feat: Clear all previous notifications if a new notification is added to a conversation (#8490) 2023-12-06 14:03:43 +05:30
Muhsin KelothandGitHub 76711d95ff chore: Change primary actor to Conversation for all the notification types. (#8435) 2023-12-06 10:43:09 +05:30
Sivin VargheseandGitHub 17faa6c3b2 fix: Undefined method `length' for nil:NilClass (#8491) 2023-12-05 16:34:29 +05:30
Sivin VargheseandGitHub c1ac354c9b fix: Remove article meta tag is not working (#8489) 2023-12-05 16:17:41 +05:30
Muhsin KelothandGitHub 449503bb94 feat: Snooze notification API (#8439) 2023-12-04 12:32:35 +05:30
Muhsin KelothandGitHub aad18e1ca4 feat: Add notification_deleted action cable event (#8431) 2023-12-01 23:06:22 +05:30
Muhsin KelothandGitHub fdc1123b18 feat: Add support for attachments(image and video) in LINE channel (#8425) 2023-12-01 07:23:35 +05:30
Anthony MeirlaenandGitHub 0974b1e705 fix: Install node 20.x in non-production docker (#8441) (#8442) 2023-11-30 16:22:34 -08:00
Muhsin KelothandGitHub bc6e3e598f feat: Delete notification by id API (#8430) 2023-11-29 22:45:11 +05:30
683f9d9a28 fix: article meta does not show up on reload (#8415)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-11-29 16:15:31 +05:30
Martín GómezandGitHub f9bf0883c6 feat: Allow more image formats in avatar uploads form (#8419) 2023-11-28 11:18:16 +05:30
ad8ba299c9 feat: Author sections in category block and category list (#8414)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-11-27 15:13:38 +05:30
Muhsin KelothandGitHub 3ae64822e1 chore: Enforce HTTP Basic Authentication for Twilio medias. (#8396) 2023-11-27 13:24:53 +05:30
Sivin VargheseandGitHub a49aaee9cf fix: Email messages in dark mode (#8300) 2023-11-24 12:14:02 +05:30
Sivin VargheseandGitHub 2d1f70eb79 feat: Update public portal colors with new design (#8230) 2023-11-23 08:16:52 +05:30
Sojan JoseandGitHub 4fc5f765de chore: Make super admin auth error readable (#8406)
- Remove the cryptic error for super admin auth and make it more readable
2023-11-22 17:26:08 -08:00
Nithin David ThomasandGitHub 7efc2726d9 fix: Updates the text alignment on articles table (#8405) 2023-11-23 02:58:00 +05:30
Pranav Raj SandGitHub 29a778697c fix: Remove validation for message signature (#8404) 2023-11-22 13:09:17 -08:00
Pranav Raj SandGitHub 90649e72bb fix: Add a .present? check for sentiment file path to avoid false positives (#8401) 2023-11-22 10:35:51 -08:00
Pranav Raj SandGitHub 1904ec7df4 fix: Update campaign routes to fix the rendering issue (#8400) 2023-11-22 09:39:41 -08:00
Nithin David ThomasandGitHub 96add30331 chore: Add analytics event for insert article feature (#8393) 2023-11-22 08:18:58 -08:00
9c7148e2ad feat: Split dashboard to chunks for build performance (#8394)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-21 14:51:05 -08:00
31c709be5c fix: Opens foreign links from article page in new tab [cw-2725] (#8304)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-11-22 01:57:58 +05:30
e750ee6d28 chore: [Snyk] Security upgrade administrate-field-active_storage from 0.4.2 to 1.0.0 (#8382)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-11-20 19:42:54 -08:00
Vishnu NarayananandGitHub 5b3f9ac1cd chore: skip node upgrade if latest on cwctl upgrade (#8336) 2023-11-20 12:48:39 +05:30
Muhsin KelothandGitHub 6c8dacfa0d feat: Facebook delivery reports (#8136) 2023-11-20 12:22:45 +05:30
Sojan feead30b0b Merge branch 'release/3.3.1' into develop 2023-11-18 10:24:35 -08:00
Sojan 4244945e7b Bump version to 3.3.1 2023-11-18 10:23:18 -08:00
Clairton Rodrigo HeinzenandGitHub 17725e4dd0 fix: Breakage of message pane when Openai integration isn't enabled (#8381)
Error when the Openai integration isn't enabled

fixes: https://github.com/chatwoot/chatwoot/issues/8379
2023-11-18 10:18:55 -08:00
Sojan d55bb63aa0 Merge branch 'release/3.3.0' into develop 2023-11-17 20:37:44 -08:00
Sojan 6e22695b85 Bump version to 3.3.0 2023-11-17 20:34:39 -08:00
Pranav Raj SandGitHub 146e46d79f fix: Disable AI Assist CTA if not required (#8375)
- Show CTAs only on production
2023-11-17 20:10:46 -08:00
Nithin David ThomasandGitHub 0af27a2387 feat: Adds support for logo in portal settings page [CW-2585] (#8354) 2023-11-17 19:58:27 -08:00
7380f0e7ce chore: Making OpenAI label suggestions optional (#8374)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-17 19:54:15 -08:00
Pranav Raj SandGitHub 2c5a0abcce fix: Update the height of the input based on the rows (#8373) 2023-11-17 17:29:44 -08:00
Sojan JoseandGitHub f23e33c1a9 perf: Remove unwanted conversation count query (#8372) 2023-11-17 16:40:40 -08:00
5588d6e344 feat: convert feature_flag to bigint [CW-2767] (#8350)
Co-authored-by: Sojan <sojan@pepalo.com>
2023-11-17 15:50:24 -08:00
59ace66c6f chore: [Snyk] Fix for vulnerabilities (#8340)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-11-17 15:24:00 -08:00
Sojan JoseandGitHub decef1ad52 chore: Add rails_panel gem for query debugging (#8370) 2023-11-18 02:30:35 +05:30
Sojan JoseandGitHub 0e19a4196b chore: Endpoint to purge portal logo (#8365) 2023-11-18 01:13:22 +05:30
Muhsin KelothandGitHub 04dd65687b feat: Changes to include delivery reports for all channels (#8329) 2023-11-16 19:43:35 +05:30
Muhsin KelothandGitHub 5f503b1a57 feat: Change slack bot and activity message appearance (#8349) 2023-11-16 12:59:52 +05:30
Sojan JoseandGitHub 5f6e974531 chore: Add website_token into request logs (#8362)
- Log website_token into request logs to identify specific widgets
2023-11-15 20:32:21 -08:00
Sojan JoseandGitHub a023eabf59 chore: Add logging for facebook errors (#8360)
- Add logging for Facebook errors
2023-11-15 14:56:21 -08:00
Sivin VargheseandGitHub cb1e25a487 feat: Change the highlight in article page on scrolling (#8330) 2023-11-15 13:51:46 +05:30
Sojan JoseandGitHub bcf7c6c45a chore: Improve account seeding (#8357) 2023-11-15 13:29:00 +05:30
Shivam MishraandGitHub b411b6ca21 feat: remove feature flag checks for message reply to (#8352) 2023-11-15 13:05:58 +05:30
Shivam MishraandGitHub cd217a3f86 feat: allow automation rules to remove labels (#8346) 2023-11-13 13:10:55 +05:30
Pranav Raj SandGitHub 6c79642450 chore: Update dashboard screenshots and the logo (#8342) 2023-11-11 23:08:05 +05:30
Nithin David ThomasandGitHub 201d476720 chore: Add meta viewport tag to public portal (#8335) 2023-11-09 20:30:58 -08:00
Muhsin KelothandGitHub c1a2e67c9e fix: Change the route change logic based on bus events, after creating a new conversation in the widget. (#8328) 2023-11-10 09:44:59 +05:30
1948bef9ac fix: Fix line break issue with link in portal header (#8323)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-11-08 20:38:38 -08:00
Sivin VargheseandGitHub 5486f39f7e feat: Update uncategorized block design (#8324) 2023-11-09 08:47:38 +05:30
f018b0013c feat: Updated the design of the article page (#8166)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-08 18:05:13 -08:00
950f085e80 feat: Updated the design of the category page (#8165)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-08 18:03:16 -08:00
268e26625b feat: Adds the ability to see the popular articles (#8152)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-08 18:01:07 -08:00
7041d86e4d feat: Updated home page design (#8106)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-11-08 17:59:03 -08:00
78398ee800 feat: Updated public portal header design (#8089)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-11-08 17:56:59 -08:00
Sojan JoseandGitHub 7b09b02737 chore: Fix trigger message for response Bot (#8322)
- Fix the bug where wrong messages was used to search response sources
2023-11-08 17:02:48 -08:00
Muhsin KelothandGitHub af7631d9f1 chore: Update MarkMessagesAsReadJob to accept delivered status (#8319) 2023-11-08 13:44:57 -08:00
Sivin VargheseandGitHub e0f33e62af feat: Adds support for image resize in the message bubble (#8182) 2023-11-08 14:11:47 +05:30
5d224f2e04 feat: allow inbox specific flags for signature toggle (#8280)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2023-11-08 10:47:22 +05:30
Gabriel QuinaandGitHub 4cb981d81b fix: Semantics across portuguese translations (#8291) 2023-11-08 10:42:57 +05:30
f891cfbf47 chore: [Snyk] Security upgrade sidekiq-cron from 1.10.1 to 1.11.0 (#8294)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-11-08 10:32:04 +05:30
Sojan JoseandGitHub e54c8a857d chore: Debug ChatGpt response (#8316)
- Add loggers to debug ChatGPT requests and responses
2023-11-07 20:07:29 -08:00
66229b0d6b chore: [Snyk] Security upgrade audited from 5.4.0 to 5.4.1 (#8305)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-11-07 17:45:32 -08:00
Muhsin KelothandGitHub 3eb1d49577 fix: Handle all types of errors in API channel webhooks (#8307) 2023-11-07 12:54:49 +05:30
Vishnu NarayananandGitHub 677888bcde feat: Add job to remove stale contact inboxes (#8096) 2023-11-06 20:24:30 -08:00
abbb4180ea chore: Add facebook business_management scope (#8004)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-11-06 18:37:41 -08:00
Pranav Raj SandGitHub d1cbc56d67 chore: Update widget config API to migrate the widget (#8303)
- Update the keys in the widget config API to support loading the widget separately from the Rails App.
2023-11-06 15:27:29 -08:00
EmanuelandGitHub b579ff832e chore: Update the case in the translation files (#8298) 2023-11-06 08:01:54 -08:00
Nithin David ThomasandGitHub 39d0748a5b feat: Lets users insert connected portal article into replies [CW-2282] (#8117)
- Lets users insert connected portal articles into replies

https://linear.app/chatwoot/issue/CW-2282/list-all-the-top-articles-from-the-connected-help-center
https://linear.app/chatwoot/issue/CW-1453/container-view-for-showing-search-input-and-result-items
2023-11-04 02:57:25 -07:00
Muhsin KelothandGitHub b4d20689b7 feat: Mark the messages as failed if the API channel webhooks fail for any reason. (#8277) 2023-11-04 12:26:28 +05:30
3b84b0fc47 feat: Creates 404 page for public portal [CW-2727] (#8293)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-11-04 04:59:44 +05:30
Nithin David ThomasandGitHub 23ea829510 chore: Refactors help center article url helper (#8269) 2023-11-04 04:18:34 +05:30
ebe9daea00 chore: Swagger documentation for contact labels APIs (#8279)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-11-03 15:09:12 +05:30
Shivam MishraandGitHub 36b6c0cb9c feat: support image height in markdown rendering of messages (#8177)
- This PR adds BaseMarkdownRenderer, it takes all the required attributes from the image node, parses the cw_image_height query and renders it.
2023-11-02 13:51:54 -07:00
Muhsin KelothandGitHub 16c36a78f0 chore: Remove sentry exceptions for the message status failed errors (#8285)
- Remove sending exceptions to Sentry after capturing the message failed errors.
2023-11-02 12:00:22 -07:00
Nithin David ThomasandGitHub 4999f7ed23 fix: Fixes wrong page size in category filter in articles page (#8270) 2023-11-02 15:19:19 +05:30
Chatwoot BotandGitHub e8f4ac632f chore: Update translations (#8255) 2023-11-01 15:13:32 -07:00
3c7854ccdf fix: Fixes error in ChatList component (#8266)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-11-02 03:23:29 +05:30
Pranav Raj SandGitHub 1e70223ed7 chore: Upgrade Cypress to 13.4.0 (#8271) 2023-10-31 19:39:34 -07:00
f34cd3ea61 chore: [Snyk] Security upgrade newrelic-sidekiq-metrics from 1.6.1 to 1.6.2 (#8254)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-10-31 18:24:18 -07:00
Muhsin KelothandGitHub 68b8d721a7 fix: Update the message status to failed if the outgoing Facebook message fails. (#8258) 2023-10-31 17:40:17 -07:00
Nithin David ThomasandGitHub d876419b27 fix: Update text area height while typing [CW-2438] (#8268) 2023-10-31 17:39:25 -07:00
8455186e9f feat: allow instagram reply_to [CW-2609] (#8248)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-31 17:31:12 -07:00
b6584ec68b feat: toggle reply to on widget based on feature flag (#8261)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-31 21:33:14 +05:30
Muhsin KelothandGitHub 29f18c7f18 fix: Handle Twilio::REST::RestError (#8257) 2023-10-31 16:23:34 +05:30
Muhsin KelothandGitHub 11b27f9805 feat: Handle Line send message/attachments errors (#8200) 2023-10-31 07:42:30 +05:30
Pranav Raj SandGitHub 653e0335c0 fix: Handle PermissionDeniedError for Dialogflow processor (#8252) 2023-10-30 13:24:03 -07:00
013dab7da6 fix: Add support for dark mode for the calendars (date and time picker). (#8250)
Co-authored-by: Liam Ashdown <liam.ashdown@debbiesvillas.co.uk>
2023-10-30 11:48:16 -07:00
Pranav Raj SandGitHub 2ba81830f3 fix: Update Instagram story rendering (#8240) 2023-10-30 11:16:14 -07:00
Sivin VargheseandGitHub dfba4770bd feat: Save sort Conversations filter (#8237) 2023-10-30 15:25:21 +05:30
LiamandGitHub 63e702ca12 fix: Fix an issue where Chat Widget Unread Message is not correctly d… (#8245) 2023-10-30 12:52:26 +05:30
Will KramerandGitHub 394fece846 docs: Update .env.example for clearer inbound Postmark instructions (#8235)
Updated the .env.example file to provide clearer instructions for setting up the Postmark inbound webhook. Many users, including myself, found the initial instructions ambiguous, leading to difficulties during setup. This change aims to simplify the process for future users by providing clearer instructions and examples.

Fixes: #8234
2023-10-27 16:06:27 -07:00
Muhsin KelothandGitHub 24fbab94c3 chore: Refactor MarkMessagesAsReadJob based on conversation id and time stamp (#8217)
- Mark all messages as read by providing the conversation ID and timestamp.
- For Instagram, ensure all previous messages that weren't marked as failed are now marked as read. This is because the read events are only triggered for the most recent message and not for any previous ones.
2023-10-27 15:21:39 -07:00
Muhsin KelothandGitHub 61e03fa33a feat: Bandwidth sms channel delivery reports (#8198)
Fixes: https://linear.app/chatwoot/issue/CW-2566/delivery-report-for-bandwidth-sms
2023-10-27 14:07:15 -07:00
Pranav Raj SandGitHub 6c4b92f7f6 fix: Revert the changes for SyncCustomFilterCountJob (#8238) 2023-10-27 10:45:48 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f023325b0e chore(deps): bump browserify-sign from 4.2.1 to 4.2.2 (#8233)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-27 07:39:48 -07:00
dd05482cb0 chore: update UI copy (#8229)
Co-authored-by: Hricha Shandily <hrichashandily@gmail.com>
2023-10-27 16:05:42 +05:30
d94108bf3f feat: show ReplyTo in widget UI (#8094)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-27 13:35:02 +05:30
ab872beb1d feat: Helper to support dynamic system theme in public portal (#8206)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-26 12:59:51 +05:30
d02cfff4cc fix: canned responses not working when signature is present (#8176)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-26 11:35:46 +05:30
Robert CokerandGitHub 736f269c49 fix: Always reset CC/BCC emails when switching conversations (#8157) 2023-10-25 21:42:36 -07:00
Pranav Raj SandGitHub b067acfd4a fix: Update the alignment of the info icon on contact info panel (#8216) 2023-10-25 21:30:14 -07:00
Sivin VargheseandGitHub 232fa6fd18 fix: Revert missing files for search (#8215) 2023-10-26 09:08:28 +05:30
Chatwoot BotandGitHub 483d55df91 chore: Update translations (#8161) 2023-10-25 19:20:30 -07:00
3e54d3654b feat: Updated the search result fly-out menu design (#8203)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-25 19:16:49 -07:00
Sojan JoseandGitHub b6831d464e chore: Update API documentation for whatsapp templates (#8211)
- Update API documentation for whatsapp templates
2023-10-25 17:06:58 -07:00
Sojan JoseandGitHub 8c67ea56e7 chore: Campaign conversations should be created in open state (#8209)
- Ensure that conversations originating from campaigns are created in the open state.
2023-10-25 13:03:08 -07:00
Muhsin KelothandGitHub 54bc4c23df fix: Telegram model specs (#8207) 2023-10-26 00:14:09 +05:30
Muhsin KelothandGitHub 58f47eb02c feat: Handle Facebook send message/attachments errors (#8197) 2023-10-25 08:59:25 -07:00
4fc62ed9ae feat: Handle Instagram send message/attachments errors (#8174)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-25 08:56:09 -07:00
4a89bab23a feat: Handle Telegram send message/attachments errors (#8173)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-25 08:36:31 -07:00
Muhsin KelothandGitHub 7438f3b157 fix: Rescue slack ChannelNotFound error (#8196) 2023-10-25 13:53:30 +05:30
Shivam MishraandGitHub 9551dbc25f feat: add gtm to vueapp (#8154) 2023-10-24 15:51:21 +05:30
Sivin VargheseandGitHub 2e54f4a67c feat: Adds an event for the Google Translate usage (#8175) 2023-10-24 15:34:51 +05:30
Shivam MishraandGitHub 6d9b2a6b33 refactor: UI flags for Reply To (#8147) 2023-10-20 14:19:19 +05:30
Sivin VargheseandGitHub 35a9acf099 feat: Adds the ability to set an emoji for help center category (#8111) 2023-10-20 13:52:30 +05:30
b9694a0818 feat: support reply to for Telegram (#8105)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-20 13:14:20 +05:30
Shivam MishraandGitHub 7416bbb25e feat: support reply to for outgoing message in WhatsApp (#8107)
- This PR enables replies to WhatsApp.
2023-10-19 13:24:46 -07:00
Muhsin KelothandGitHub b94c89ebf1 fix: Show sent status for messenger channel (#8145) 2023-10-19 13:22:19 +05:30
Muhsin KelothandGitHub 78ce8a4652 feat: Add support for Instagram delivery reports (#8125) 2023-10-18 23:42:34 -07:00
Muhsin KelothandGitHub 04c874fe35 feat: Add delivery reports for API channel (#8116) 2023-10-18 10:11:13 +05:30
Sojan eabe548520 Merge branch 'release/3.2.0' into develop 2023-10-17 17:55:42 -07:00
Sojan f96b54e57f Bump version to 3.2.0 2023-10-17 17:54:38 -07:00
Pranav Raj SandGitHub d3bf828524 chore: Adds hasConversation flag in chatwoot:on-start-conversation event (#8132) 2023-10-17 16:24:00 -07:00
c20415f3ba test: reset Current.user before each example (#8126)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-10-17 15:42:45 -07:00
Pranav Raj SandGitHub 6b2ed4fe79 fix: Add reset password for edit agent screen (#8130) 2023-10-17 12:54:05 -07:00
Shivam MishraandGitHub eca9ca90f1 feat: Add June event for reply to (#8118) 2023-10-17 09:51:28 -07:00
Shivam MishraandGitHub 9018ec9678 test: reset Current.user after spec (#8123) 2023-10-17 18:06:54 +05:30
Shivam MishraandGitHub e55fe33f1b test: fix flaky test by adding margin for time diff (#8121) 2023-10-17 14:20:40 +05:30
3b0788c3be chore: Update translations (#8024)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-10-16 19:55:25 -07:00
Vishnu NarayananandGitHub 626e67b859 feat: add autoupdate for cwctl (#8108)
- During cwctl --upgrade, check for the latest version of cwctl from master branch
- Upgrade cwctl if a newer version is found
2023-10-16 19:43:44 -07:00
Nithin David ThomasandGitHub 28db18ebbd chore: Shows articles in dark mode for widget (#8115) 2023-10-16 16:59:15 +05:30
b28721e10b feat: Creates components for Article Search in Reply [CW-2285] (#7957)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-10-16 15:02:02 +05:30
Shivam MishraandGitHub f77db4d814 refactor: disable bubble by flag (#8109) 2023-10-13 21:34:22 +05:30
62d8ec7edb feat: support reply to for incoming messages on facebook (#8076)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-13 16:33:50 +05:30
7b09fa4a03 chore(snyk): Upgrade gems to fix SNYK-RUBY-RACK-1061917 (#8104)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-10-13 09:24:50 +05:30
Muhsin KelothandGitHub 980013abae feat: Add delivery reports for live chat (#8092) 2023-10-12 20:48:20 +05:30
Shivam MishraandGitHub 5c729ccea5 chore: Add null checks (#8100) 2023-10-12 19:39:19 +05:30
Vishnu NarayananandGitHub ae52ca87aa fix: Handle empty status in conversation controller (#8091)
Fixes CW-2644
2023-10-12 17:11:04 +05:30
Vishnu NarayananandGitHub 415bb23c37 fix: Handle invalid metric in ReportsController (#8086)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>

Fixes CW-2643
2023-10-12 17:01:23 +05:30
Pranav Raj SandGitHub 9e173fca3c fix: Return 404 if conversation is nil (#8098) 2023-10-12 17:00:51 +05:30
Muhsin KelothandGitHub bd918ee506 fix: Change the message status to failed if the Twilio message delivery status is undelivered (#8097) 2023-10-12 15:27:38 +05:30
Shivam MishraandGitHub 7c9884b853 feat: Remove unused prop (#8093) 2023-10-12 12:14:39 +05:30
7ffa669c5c feat: Implement message bubble reply to (#8068)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-11 22:04:12 +05:30
0bc20873f6 feat: Add delivery status for Twilio Channel (#8082)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-11 21:17:46 +05:30
88de3359a5 fix: Long names hide the resolve button (#8083)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-11 13:25:20 +05:30
Sojan JoseandGitHub a3d008da06 chore: Ensure null validation for private attribute in messages (#8085) 2023-10-11 13:16:59 +05:30
cbae95422d feat: Implement reply to for reply editor (#8063)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-10 19:13:12 +05:30
Shivam MishraandGitHub 081c845c56 chore: Remove twitter actions (#8079) 2023-10-10 16:48:58 +05:30
Pranav Raj SandGitHub 1e54d6deb3 fix: Update duplicate ids for dashboard app frame (#8077) 2023-10-10 16:46:32 +05:30
Shivam MishraandGitHub bb5c6a416a fix: insert position for canned responses (#8069) 2023-10-10 10:36:10 +05:30
6a07251007 feat: Add the ability to self-assign conversations in macros (#8048)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-10 09:38:23 +05:30
Sivin VargheseandGitHub 759a66dd21 feat: Add the ability to send attachment in new conversation (#7913) 2023-10-10 09:34:36 +05:30
Vishnu NarayananandGitHub 8189dd932c feat: Report cwctl events to hub (#8009) 2023-10-10 09:16:03 +05:30
b6ba0f343e feat: Reverse the contact merge (#8057)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-10 08:20:48 +05:30
Sivin VargheseandGitHub 86ca90aa15 feat: Support image resize in message signature (#8042) 2023-10-09 13:20:12 +05:30
Muhsin KelothandGitHub 26e8877cd9 feat: Support link unfurling for all the channels within the same connected channel account. (#8033) 2023-10-08 17:55:03 +05:30
1b63adfb2e feat: add option for reply to in context menu (#8043)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-05 20:01:20 +05:30
e27274a5a8 chore: Adds a bus event to insert text at cursor in editor (#7968)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-10-05 14:37:01 +05:30
e5c198f839 refactor: Remove redundant accounts/get (#8056)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-10-05 13:54:04 +05:30
Sojan JoseandGitHub 72ba429159 fix: Show sources in bot response only if documents are present (#8055)
- show sources section in bot only based on appropriate conditions
2023-10-05 00:11:01 -07:00
Pranav Raj SandGitHub 3227cf7ff3 fix: Update query to support multiple tag conditions (#8054) 2023-10-05 10:47:42 +05:30
f0740120a8 chore: Add a fix for mouse up event in modal (#7950)
Co-authored-by: BikashSah999 <51731962+BikashSah999@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-10-05 10:44:31 +05:30
3ea54065b1 feat: Upgrade prompt for help center (#8010)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-10-05 08:34:21 +05:30
Sojan JoseandGitHub 6f19546c3c feat: Add article messages along with bot responses (#7993)
ref: https://linear.app/chatwoot/issue/CW-2464/bot-should-also-return-links-to-the-information
2023-10-04 15:40:59 -07:00
1455 changed files with 30722 additions and 27782 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
+21 -2
View File
@@ -81,7 +81,7 @@ SMTP_OPENSSL_VERIFY_MODE=peer
# Mail Incoming
# This is the domain set for the reply emails when conversation continuity is enabled
MAILER_INBOUND_EMAIL_DOMAIN=
# Set this to appropriate ingress channel with regards to incoming emails
# Set this to the appropriate ingress channel with regards to incoming emails
# Possible values are :
# relay for Exim, Postfix, Qmail
# mailgun for Mailgun
@@ -91,10 +91,20 @@ MAILER_INBOUND_EMAIL_DOMAIN=
RAILS_INBOUND_EMAIL_SERVICE=
# Use one of the following based on the email ingress service
# Ref: https://edgeguides.rubyonrails.org/action_mailbox_basics.html
# Set this to a password of your choice and use it in the Inbound webhook
RAILS_INBOUND_EMAIL_PASSWORD=
MAILGUN_INGRESS_SIGNING_KEY=
MANDRILL_INGRESS_API_KEY=
# Creating Your Inbound Webhook Instructions for Postmark and Sendgrid:
# Inbound webhook URL format:
# https://actionmailbox:[YOUR_RAILS_INBOUND_EMAIL_PASSWORD]@[YOUR_CHATWOOT_DOMAIN.COM]/rails/action_mailbox/[RAILS_INBOUND_EMAIL_SERVICE]/inbound_emails
# Note: Replace the values inside the brackets; do not include the brackets themselves.
# Example: https://actionmailbox:mYRandomPassword3@chatwoot.example.com/rails/action_mailbox/postmark/inbound_emails
# For Postmark
# Ensure the 'Include raw email content in JSON payload' checkbox is selected in the inbound webhook section.
# Storage
ACTIVE_STORAGE_SERVICE=local
@@ -176,6 +186,9 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
# MICROSOFT CLARITY
# MS_CLARITY_TOKEN=xxxxxxxxx
# GOOGLE_TAG_MANAGER
# GOOGLE_TAG = GTM-XXXXXXX
## Scout
## https://scoutapm.com/docs/ruby/configuration
# SCOUT_KEY=YOURKEY
@@ -210,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
@@ -240,3 +253,9 @@ AZURE_APP_SECRET=
# Sentiment analysis model file path
SENTIMENT_FILE_PATH=
# Housekeeping/Performance related configurations
# Set to true if you want to remove stale contact inboxes
# contact_inboxes with no conversation older than 90 days will be removed
# REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false
+3 -10
View File
@@ -4,13 +4,13 @@ module.exports = {
'prettier',
'plugin:vue/recommended',
'plugin:storybook/recommended',
'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',
@@ -55,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;
},
}));
+21 -17
View File
@@ -15,7 +15,7 @@ gem 'browser'
gem 'hashie'
gem 'jbuilder'
gem 'kaminari'
gem 'responders'
gem 'responders', '>= 3.1.1'
gem 'rest-client'
gem 'telephone_number'
gem 'time_diff'
@@ -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,21 +64,21 @@ gem 'activerecord-import'
gem 'dotenv-rails'
gem 'foreman'
gem 'puma'
gem 'webpacker'
gem 'vite_rails'
# metrics on heroku
gem 'barnes'
##--- gems for authentication & authorization ---##
gem 'devise'
gem 'devise', '>= 4.9.3'
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
gem 'devise_token_auth'
# authorization
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.19.0'
gem 'administrate-field-active_storage'
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/
@@ -92,7 +94,7 @@ gem 'twitty', '~> 0.1.5'
# facebook client
gem 'koala'
# slack client
gem 'slack-ruby-client', '~> 2.0.0'
gem 'slack-ruby-client', '~> 2.2.0'
# for dialogflow integrations
gem 'google-cloud-dialogflow-v2'
gem 'grpc'
@@ -107,16 +109,16 @@ gem 'google-cloud-translate-v3'
gem 'ddtrace', require: false
gem 'elastic-apm', require: false
gem 'newrelic_rpm', require: false
gem 'newrelic-sidekiq-metrics', require: false
gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false
gem 'scout_apm', require: false
gem 'sentry-rails', '>= 5.11.0', require: false
gem 'sentry-rails', '>= 5.14.0', require: false
gem 'sentry-ruby', require: false
gem 'sentry-sidekiq', '>= 5.11.0', 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.10.1'
gem 'sidekiq-cron', '>= 1.12.0'
##-- Push notification service --##
gem 'fcm'
@@ -154,15 +156,15 @@ gem 'stripe'
gem 'faker'
# Include logrange conditionally in intializer using env variable
gem 'lograge', '~> 0.13.0', require: false
gem 'lograge', '~> 0.14.0', require: false
# worked with microsoft refresh token
gem 'omniauth-oauth2'
gem 'audited', '~> 5.4', '>= 5.4.0'
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'
@@ -198,8 +200,10 @@ group :development do
gem 'squasher'
# profiling
gem 'rack-mini-profiler', '>= 3.1.1', require: false
gem 'rack-mini-profiler', '>= 3.2.0', require: false
gem 'stackprof'
# Should install the associated chrome extension to view query logs
gem 'meta_request'
end
group :test do
@@ -222,7 +226,7 @@ group :development, :test do
gem 'byebug', platform: :mri
gem 'climate_control'
gem 'debug', '~> 1.8'
gem 'factory_bot_rails'
gem 'factory_bot_rails', '>= 6.4.3'
gem 'listen'
gem 'mock_redis'
gem 'pry-rails'
+96 -86
View File
@@ -105,30 +105,30 @@ 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 (0.4.2)
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)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_extras (7.1.0)
audited (5.4.0)
activerecord (>= 5.0, < 7.2)
request_store (~> 1.2)
audited (5.4.1)
activerecord (>= 5.0, < 7.7)
activesupport (>= 5.0, < 7.7)
aws-eventstream (1.2.0)
aws-partitions (1.760.0)
aws-sdk-core (3.171.1)
@@ -148,7 +148,8 @@ GEM
barnes (0.0.9)
multi_json (~> 1)
statsd-ruby (~> 1.1)
bcrypt (3.1.18)
base64 (0.1.1)
bcrypt (3.1.19)
bindex (0.8.1)
blingfire (0.1.8)
bootsnap (1.16.0)
@@ -168,13 +169,13 @@ 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
crass (1.0.6)
csv-safe (3.2.1)
cypress-on-rails (1.13.1)
cypress-on-rails (1.16.0)
rack
database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3)
@@ -182,7 +183,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.3)
date (3.3.4)
ddtrace (1.11.1)
debase-ruby_core_source (>= 0.10.16, <= 3.2.0)
libdatadog (~> 2.0.0.1.0)
@@ -193,7 +194,7 @@ GEM
irb (>= 1.5.0)
reline (>= 0.3.1)
declarative (0.0.20)
devise (4.9.2)
devise (4.9.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -215,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)
@@ -229,10 +231,10 @@ GEM
facebook-messenger (2.0.1)
httparty (~> 0.13, >= 0.13.7)
rack (>= 1.4.5)
factory_bot (6.2.1)
factory_bot (6.4.5)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faker (3.2.0)
i18n (>= 1.8.11, < 2)
@@ -255,13 +257,13 @@ GEM
fcm (1.0.8)
faraday (>= 1.0.0, < 3.0)
googleauth (~> 1)
ffi (1.15.5)
ffi (1.16.3)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
flag_shih_tzu (0.3.23)
foreman (0.87.2)
fugit (1.8.1)
fugit (1.9.0)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
gapic-common (0.18.0)
@@ -273,7 +275,7 @@ GEM
googleauth (~> 1.0)
grpc (~> 1.36)
geocoder (1.8.1)
gli (2.21.0)
gli (2.21.1)
globalid (1.2.1)
activesupport (>= 6.1)
gmail_xoauth (0.4.2)
@@ -434,12 +436,12 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
lograge (0.13.0)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.21.3)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -450,14 +452,17 @@ GEM
marcel (1.0.2)
maxminddb (0.1.22)
memoist (0.16.2)
meta_request (0.7.4)
rack-contrib (>= 1.1, < 3)
railties (>= 3.0.0, < 7.1)
method_source (1.0.0)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.4)
minitest (5.20.0)
mini_portile2 (2.8.5)
minitest (5.21.2)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
@@ -468,29 +473,30 @@ GEM
activerecord (>= 5.2)
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.3.7)
net-imap (0.4.9)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
net-protocol (0.2.2)
timeout
net-smtp (0.3.3)
net-smtp (0.3.4)
net-protocol
netrc (0.11.0)
newrelic-sidekiq-metrics (1.6.1)
newrelic_rpm (~> 8)
newrelic-sidekiq-metrics (1.6.2)
newrelic_rpm (>= 8.0.0)
sidekiq
newrelic_rpm (8.16.0)
nio4r (2.5.9)
nokogiri (1.15.4)
newrelic_rpm (9.6.0)
base64
nio4r (2.7.0)
nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.15.4-arm64-darwin)
nokogiri (1.16.0-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.4-x86_64-darwin)
nokogiri (1.16.0-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.15.4-x86_64-linux)
nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
@@ -506,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
@@ -548,22 +554,24 @@ 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)
raabro (1.4.0)
racc (1.7.1)
racc (1.7.3)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-contrib (2.4.0)
rack (< 4)
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-mini-profiler (3.1.1)
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)
@@ -597,13 +605,13 @@ GEM
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
rake (13.1.0)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (5.0.6)
redis-client (>= 0.9.0)
redis-client (0.17.0)
redis-client (0.19.1)
connection_pool
redis-namespace (1.10.0)
redis (>= 4)
@@ -616,7 +624,7 @@ GEM
uber (< 0.2.0)
request_store (1.5.1)
rack (>= 1.4)
responders (3.1.0)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rest-client (2.1.0)
@@ -701,24 +709,23 @@ GEM
activerecord (>= 4)
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.11.0)
sentry-rails (5.14.0)
railties (>= 5.0)
sentry-ruby (~> 5.11.0)
sentry-ruby (5.11.0)
sentry-ruby (~> 5.14.0)
sentry-ruby (5.14.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-sidekiq (5.11.0)
sentry-ruby (~> 5.11.0)
sentry-sidekiq (5.14.0)
sentry-ruby (~> 5.14.0)
sidekiq (>= 3.0)
sexp_processor (4.17.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.1.3)
sidekiq (7.2.1)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
redis-client (>= 0.14.0)
sidekiq-cron (1.10.1)
redis-client (>= 0.19.0)
sidekiq-cron (1.12.0)
fugit (~> 1.8)
globalid (>= 1.0.1)
sidekiq (>= 6)
@@ -732,13 +739,12 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-ruby-client (2.0.0)
slack-ruby-client (2.2.0)
faraday (>= 2.0)
faraday-mashify
faraday-multipart
gli
hashie
websocket-driver
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
@@ -746,7 +752,7 @@ GEM
spring-watcher-listen (2.1.0)
listen (>= 2.7, < 4.0)
spring (>= 4)
sprockets (4.2.0)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2)
@@ -759,12 +765,12 @@ GEM
stripe (8.5.0)
telephone_number (1.4.20)
test-prof (1.2.1)
thor (1.2.2)
tilt (2.2.0)
thor (1.3.0)
tilt (2.3.0)
time_diff (0.3.0)
activesupport
i18n
timeout (0.4.0)
timeout (0.4.1)
trailblazer-option (0.1.2)
twilio-ruby (5.77.0)
faraday (>= 0.9, < 3.0)
@@ -789,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)
@@ -804,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)
@@ -817,7 +825,7 @@ GEM
working_hours (1.4.1)
activesupport (>= 3.2)
tzinfo
zeitwerk (2.6.11)
zeitwerk (2.6.12)
PLATFORMS
arm64-darwin-20
@@ -834,12 +842,12 @@ DEPENDENCIES
active_record_query_trace
activerecord-import
acts-as-taggable-on
administrate (>= 0.19.0)
administrate-field-active_storage
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.0)
audited (~> 5.4, >= 5.4.1)
aws-sdk-s3
azure-storage-blob!
barnes
@@ -856,7 +864,7 @@ DEPENDENCIES
database_cleaner
ddtrace
debug (~> 1.8)
devise
devise (>= 4.9.3)
devise-secure_password!
devise_token_auth
dotenv-rails
@@ -864,7 +872,7 @@ DEPENDENCIES
elastic-apm
email_reply_trimmer
facebook-messenger
factory_bot_rails
factory_bot_rails (>= 6.4.3)
faker
fcm
flag_shih_tzu
@@ -892,13 +900,15 @@ DEPENDENCIES
line-bot-api
liquid
listen
lograge (~> 0.13.0)
lograge (~> 0.14.0)
maxminddb
meta_request
mock_redis
neighbor
newrelic-sidekiq-metrics
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)
@@ -911,12 +921,12 @@ DEPENDENCIES
pundit
rack-attack (>= 6.7.0)
rack-cors
rack-mini-profiler (>= 3.1.1)
rack-mini-profiler (>= 3.2.0)
rack-timeout
rails (~> 7.0.8.0)
redis
redis-namespace
responders
responders (>= 3.1.1)
rest-client
reverse_markdown
rspec-rails
@@ -928,14 +938,14 @@ DEPENDENCIES
scout_apm
scss_lint
seed_dump
sentry-rails (>= 5.11.0)
sentry-rails (>= 5.14.0)
sentry-ruby
sentry-sidekiq (>= 5.11.0)
sentry-sidekiq (>= 5.14.0)
shoulda-matchers
sidekiq (>= 7.1.3)
sidekiq-cron (>= 1.10.1)
sidekiq (>= 7.2.1)
sidekiq-cron (>= 1.12.0)
simplecov (= 0.17.1)
slack-ruby-client (~> 2.0.0)
slack-ruby-client (~> 2.2.0)
spring
spring-watcher-listen
squasher
@@ -949,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
+2 -2
View File
@@ -1,3 +1,3 @@
backend: RAILS_ENV=test bin/rails s -p 5050
frontend: bin/webpack-dev-server
worker: dotenv RAILS_ENV=test bundle exec sidekiq -C config/sidekiq.yml
frontend: bin/vite
worker: RAILS_ENV=test dotenv bundle exec sidekiq -C config/sidekiq.yml
+11 -12
View File
@@ -1,10 +1,12 @@
<p align="center">
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/brand.svg" alt="Woot-logo" width="240" />
<img src="https://user-images.githubusercontent.com/2246121/282256557-1570674b-d142-4198-9740-69404cc6a339.png#gh-light-mode-only" width="100%" alt="Chat dashboard dark mode"/>
<img src="https://user-images.githubusercontent.com/2246121/282256632-87f6a01b-6467-4e0e-8a93-7bbf66d03a17.png#gh-dark-mode-only" width="100%" alt="Chat dashboard"/>
<p align="center">Customer engagement suite, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.</p>
</p>
___
<p align="center">
# Chatwoot
Customer engagement suite, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.
<p>
<a href="https://heroku.com/deploy?template=https://github.com/chatwoot/chatwoot/tree/master" alt="Deploy to Heroku">
<img width="150" alt="Deploy" src="https://www.herokucdn.com/deploy/button.svg"/>
</a>
@@ -13,9 +15,7 @@
</a>
</p>
___
<p align="center">
<p>
<a href="https://codeclimate.com/github/chatwoot/chatwoot/maintainability"><img src="https://api.codeclimate.com/v1/badges/e6e3f66332c91e5a4c0c/maintainability" alt="Maintainability"></a>
<img src="https://img.shields.io/circleci/build/github/chatwoot/chatwoot" alt="CircleCI Badge">
<a href="https://hub.docker.com/r/chatwoot/chatwoot/"><img src="https://img.shields.io/docker/pulls/chatwoot/chatwoot" alt="Docker Pull Badge"></a>
@@ -29,9 +29,8 @@ ___
<a href="https://artifacthub.io/packages/helm/chatwoot/chatwoot"><img src="https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/artifact-hub" alt="Artifact HUB"></a>
</p>
<img src="https://chatwoot-public-assets.s3.amazonaws.com/github/screenshot.png" width="100%" alt="Chat dashboard"/>
<img src="https://user-images.githubusercontent.com/2246121/282255783-ee8a50c9-f42d-4752-8201-2d59965a663d.png#gh-light-mode-only" width="100%" alt="Chat dashboard dark mode"/>
<img src="https://user-images.githubusercontent.com/2246121/282255784-3d1994ec-d895-4ff5-ac68-d819987e1869.png#gh-dark-mode-only" width="100%" alt="Chat dashboard"/>
Chatwoot is an open-source, self-hosted customer engagement suite. Chatwoot lets you view and manage your customer data, communicate with them irrespective of which medium they use, and re-engage them based on their profile.
@@ -119,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
@@ -1 +1 @@
3.1.0
3.3.1
+1 -1
View File
@@ -1 +1 @@
2.4.0
2.7.0
+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
@@ -25,7 +25,6 @@
@import 'components/flashes';
@import 'components/form-actions';
@import 'components/main-content';
@import 'components/navigation';
@import 'components/pagination';
@import 'components/search';
@import 'components/reports';
@@ -1,7 +1,7 @@
html {
background-color: $color-white;
box-sizing: border-box;
font-size: 10px;
font-size: 16px;
-webkit-font-smoothing: antialiased;
}
@@ -16,8 +16,8 @@
.attribute-data {
float: left;
margin-bottom: $base-spacing;
margin-left: 2rem;
width: calc(84% - 1rem);
margin-left: 1.25rem;
width: calc(84% - 0.625rem);
}
.attribute--nested {
@@ -9,22 +9,22 @@
.field-unit__label {
float: left;
margin-left: 1rem;
margin-left: 0.625rem;
text-align: right;
width: calc(15% - 1rem);
width: calc(15% - 0.625rem);
}
.field-unit__field {
float: left;
margin-left: 2rem;
max-width: 50rem;
margin-left: 1.25rem;
max-width: 31.15rem;
width: 100%;
}
.field-unit--nested {
border: $base-border;
margin-left: 7.5%;
max-width: 60rem;
max-width: 37.5rem;
padding: $small-spacing;
width: 100%;
@@ -1,3 +1,3 @@
.form-actions {
margin-left: calc(15% + 2rem);
margin-left: calc(15% + 1.25rem);
}
@@ -13,6 +13,10 @@
table {
font-size: $font-size-small;
}
form {
margin-top: $space-two;
}
}
.main-content__header {
@@ -20,7 +24,7 @@
background-color: $color-white;
border-bottom: 1px solid $color-border;
display: flex;
min-height: 5.6rem;
min-height: 3.5rem;
padding: $space-small $space-normal;
}
@@ -1,88 +0,0 @@
.logo-brand {
margin-bottom: $space-normal;
padding: $space-normal $space-smaller $space-small;
text-align: left;
img {
margin-bottom: $space-smaller;
max-height: 3rem;
}
}
.navigation {
background: $white;
border-right: 1px solid $color-border;
display: flex;
flex-direction: column;
font-size: $font-size-default;
font-weight: $font-weight-medium;
height: 100%;
justify-content: flex-start;
left: 0;
margin: 0;
overflow: auto;
padding: $space-normal;
position: fixed;
top: 0;
width: 21rem;
z-index: 1023;
li {
align-items: center;
display: flex;
font-size: $font-size-small;
a {
color: $color-gray;
text-decoration: none;
}
i {
min-width: $space-medium;
}
}
hr {
margin: $space-slab;
}
}
.navigation__link {
background-color: transparent;
color: $color-gray;
display: block;
line-height: 1;
margin-bottom: $space-smaller;
padding: $space-small;
&:hover {
color: $blue;
a {
color: $blue;
}
}
&.navigation__link--active {
background-color: $color-background;
border-radius: $base-border-radius;
color: $blue;
a {
color: $blue;
}
}
}
.logout {
bottom: $space-normal;
left: $space-normal;
position: fixed;
}
.app-version {
color: $color-gray;
font-size: $font-size-small;
padding-top: $space-smaller;
}
@@ -1,7 +1,7 @@
.search {
margin-left: auto;
margin-right: 2rem;
max-width: 44rem;
margin-right: 1.25rem;
max-width: 27.5rem;
position: relative;
width: 100%;
}
@@ -1,10 +1,10 @@
// Typography
$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif !default;
$heading-font-family: $base-font-family !default;
$base-font-size: 14px !default;
$base-font-size: 16px !default;
$base-line-height: 1.5 !default;
$heading-line-height: 1.2 !default;
@@ -1,30 +1,30 @@
// Font sizes
$font-size-nano: 0.8rem;
$font-size-micro: 1.0rem;
$font-size-mini: 1.2rem;
$font-size-small: 1.4rem;
$font-size-default: 1.6rem;
$font-size-medium: 1.8rem;
$font-size-large: 2.2rem;
$font-size-big: 2.4rem;
$font-size-bigger: 3.0rem;
$font-size-mega: 3.4rem;
$font-size-giga: 4.0rem;
$font-size-nano: 0.5rem;
$font-size-micro: 0.675rem;
$font-size-mini: 0.75rem;
$font-size-small: 0.875rem;
$font-size-default: 1rem;
$font-size-medium: 1.125rem;
$font-size-large: 1.375rem;
$font-size-big: 1.5rem;
$font-size-bigger: 1.75rem;
$font-size-mega: 2.125rem;
$font-size-giga: 2.5rem;
// spaces
$zero: 0;
$space-micro: 0.2rem;
$space-smaller: 0.4rem;
$space-small: 0.8rem;
$space-one: 1rem;
$space-slab: 1.2rem;
$space-normal: 1.6rem;
$space-two: 2.0rem;
$space-medium: 2.4rem;
$space-large: 3.2rem;
$space-larger: 4.8rem;
$space-jumbo: 6.4rem;
$space-mega: 10.0rem;
$space-micro: 0.125rem;
$space-smaller: 0.25rem;
$space-small: 0.5rem;
$space-one: 0.675rem;
$space-slab: 0.75rem;
$space-normal: 1rem;
$space-two: 1.25rem;
$space-medium: 1.5rem;
$space-large: 2rem;
$space-larger: 3rem;
$space-jumbo: 4rem;
$space-mega: 6.25rem;
// font-weight
$font-weight-feather: 100;
@@ -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;
@@ -25,7 +25,9 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
build_contact_inbox
build_message
end
rescue Koala::Facebook::AuthenticationError
rescue Koala::Facebook::AuthenticationError => e
Rails.logger.warn("Facebook authentication error for inbox: #{@inbox.id} with error: #{e.message}")
Rails.logger.error e
@inbox.channel.authorization_error!
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
@@ -93,6 +95,9 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
message_type: @message_type,
content: response.content,
source_id: response.identifier,
content_attributes: {
in_reply_to_external_id: response.in_reply_to_external_id
},
sender: @outgoing_echo ? nil : @contact_inbox.contact
}
end
@@ -105,11 +110,15 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
}
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def contact_params
begin
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
result = k.get_object(@sender_id) || {}
rescue Koala::Facebook::AuthenticationError
rescue Koala::Facebook::AuthenticationError => e
Rails.logger.warn("Facebook authentication error for inbox: #{@inbox.id} with error: #{e.message}")
Rails.logger.error e
@inbox.channel.authorization_error!
raise
rescue Koala::Facebook::ClientError => e
@@ -127,4 +136,6 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
end
process_contact_params_result(result)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
end
@@ -20,7 +20,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
ActiveRecord::Base.transaction do
build_message
end
rescue Koala::Facebook::AuthenticationError
rescue Koala::Facebook::AuthenticationError => e
Rails.logger.warn("Instagram authentication error for inbox: #{@inbox.id} with error: #{e.message}")
Rails.logger.error e
@inbox.channel.authorization_error!
raise
rescue StandardError => e
@@ -46,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
@@ -76,6 +82,10 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
message[:reply_to][:story] if message[:reply_to].present? && message[:reply_to][:story].present?
end
def message_reply_attributes
message[:reply_to][:mid] if message[:reply_to].present? && message[:reply_to][:mid].present?
end
def build_message
return if @outgoing_echo && already_sent_from_chatwoot?
return if message_content.blank? && all_unsupported_files?
@@ -112,14 +122,20 @@ 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,
source_id: message_identifier,
content: message_content,
sender: @outgoing_echo ? nil : contact
sender: @outgoing_echo ? nil : contact,
content_attributes: {
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?
+37 -4
View File
@@ -9,11 +9,11 @@ class Messages::MessageBuilder
@user = user
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
@automation_rule = @params&.dig(:content_attributes, :automation_rule_id)
@automation_rule = content_attributes&.dig(:automation_rule_id)
return unless params.instance_of?(ActionController::Parameters)
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
@in_reply_to = content_attributes&.dig(:in_reply_to)
@items = content_attributes&.dig(:items)
end
def perform
@@ -26,6 +26,38 @@ class Messages::MessageBuilder
private
# Extracts content attributes from the given params.
# - Converts ActionController::Parameters to a regular hash if needed.
# - Attempts to parse a JSON string if content is a string.
# - Returns an empty hash if content is not present, if there's a parsing error, or if it's an unexpected type.
def content_attributes
params = convert_to_hash(@params)
content_attributes = params.fetch(:content_attributes, {})
return parse_json(content_attributes) if content_attributes.is_a?(String)
return content_attributes if content_attributes.is_a?(Hash)
{}
end
# Converts the given object to a hash.
# If it's an instance of ActionController::Parameters, converts it to an unsafe hash.
# Otherwise, returns the object as-is.
def convert_to_hash(obj)
return obj.to_unsafe_h if obj.instance_of?(ActionController::Parameters)
obj
end
# Attempts to parse a string as JSON.
# If successful, returns the parsed hash with symbolized names.
# If unsuccessful, returns nil.
def parse_json(content)
JSON.parse(content, symbolize_names: true)
rescue JSON::ParserError
{}
end
def process_attachments
return if @attachments.blank?
@@ -117,7 +149,8 @@ class Messages::MessageBuilder
content_type: @params[:content_type],
items: @items,
in_reply_to: @in_reply_to,
echo_id: @params[:echo_id]
echo_id: @params[:echo_id],
source_id: @params[:source_id]
}.merge(external_created_at).merge(automation_rule_id).merge(campaign_id).merge(template_params)
end
end
+8 -6
View File
@@ -1,22 +1,20 @@
class NotificationBuilder
pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!]
pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!, :secondary_actor]
def perform
return unless user_subscribed_to_notification?
build_notification
end
private
def secondary_actor
def current_user
Current.user
end
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,11 +23,15 @@ 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,
primary_actor: primary_actor,
secondary_actor: secondary_actor
# secondary_actor is secondary_actor if present, else current_user
secondary_actor: secondary_actor || current_user
)
end
end
+22 -1
View File
@@ -15,7 +15,10 @@ class V2::ReportBuilder
end
def timeseries
send(params[:metric])
return send(params[:metric]) if metric_valid?
Rails.logger.error "ReportBuilder: Invalid metric - #{params[:metric]}"
{}
end
# For backward compatible with old report
@@ -43,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
@@ -53,6 +64,16 @@ class V2::ReportBuilder
private
def metric_valid?
%w[conversations_count
incoming_messages_count
outgoing_messages_count
avg_first_response_time
avg_resolution_time reply_time
resolutions_count
reply_time].include?(params[:metric])
end
def inbox
@inbox ||= account.inboxes.find(params[:id])
end
@@ -9,7 +9,6 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::B
mergee_contact: @mergee_contact
)
contact_merge_action.perform
render json: @base_contact
end
private
@@ -10,11 +10,18 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController
def show; end
def create
@agent_bot = Current.account.agent_bots.create!(permitted_params)
@agent_bot = Current.account.agent_bots.create!(permitted_params.except(:avatar_url))
process_avatar_from_url
end
def update
@agent_bot.update!(permitted_params)
@agent_bot.update!(permitted_params.except(:avatar_url))
process_avatar_from_url
end
def avatar
@agent_bot.avatar.purge if @agent_bot.avatar.attached?
@agent_bot
end
def destroy
@@ -30,6 +37,10 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController
end
def permitted_params
params.permit(:name, :description, :outgoing_url, :bot_type, bot_config: [:csml_content])
params.permit(:name, :description, :outgoing_url, :avatar, :avatar_url, :bot_type, bot_config: [:csml_content])
end
def process_avatar_from_url
::Avatar::AvatarFromUrlJob.perform_later(@agent_bot, params[:avatar_url]) if params[:avatar_url].present?
end
end
@@ -10,7 +10,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
@articles_count = @all_articles.count
@articles = if list_params[:category_slug].present?
@all_articles.order_by_position.page(@current_page).per(50)
@all_articles.order_by_position.page(@current_page)
else
@all_articles.order_by_updated_at.page(@current_page)
end
@@ -48,7 +48,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
def category_params
params.require(:category).permit(
:name, :description, :position, :slug, :locale, :parent_category_id, :associated_category_id
:name, :description, :position, :slug, :locale, :icon, :parent_category_id, :associated_category_id
)
end
@@ -83,14 +83,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
@contact = Current.account.contacts.new(permitted_params.except(:avatar_url))
@contact.save!
@contact_inbox = build_contact_inbox
process_avatar
process_avatar_from_url
end
end
def update
@contact.assign_attributes(contact_update_params)
@contact.save!
process_avatar if permitted_params[:avatar].present? || permitted_params[:avatar_url].present?
process_avatar_from_url
end
def destroy
@@ -174,7 +174,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
@contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
end
def process_avatar
def process_avatar_from_url
::Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end
@@ -14,7 +14,8 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
def set_agent
@agent = Current.account.users.find_by(id: params[:assignee_id])
@conversation.update_assignee(@agent)
@conversation.assignee = @agent
@conversation.save!
render_agent
end
@@ -18,6 +18,15 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
end
end
def retry
return if message.blank?
message.update!(status: :sent, content_attributes: {})
::SendReplyJob.perform_later(message.id)
rescue StandardError => e
render_could_not_create_error(e.message)
end
def translate
return head :ok if already_translated_content_available?
@@ -60,13 +60,26 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def toggle_status
if params[:status]
# 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
@@ -110,8 +123,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def assign_conversation
@agent = Current.account.users.find(current_user.id)
@conversation.update_assignee(@agent)
@conversation.assignee = current_user
@conversation.save!
end
def conversation
@@ -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
@@ -1,14 +1,15 @@
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
RESULTS_PER_PAGE = 15
include DateRangeHelper
before_action :fetch_notification, only: [:update]
before_action :fetch_notification, only: [:update, :destroy, :snooze]
before_action :set_primary_actor, only: [:read_all]
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
@@ -28,11 +29,21 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
render json: @notification
end
def destroy
@notification.destroy
head :ok
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
def snooze
@notification.update(snoozed_until: parse_date_time(params[:snoozed_until].to_s)) if params[:snoozed_until]
render json: @notification
end
private
def set_primary_actor
@@ -50,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
@@ -47,6 +47,11 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
head :ok
end
def logo
@portal.logo.purge if @portal.logo.attached?
head :ok
end
def process_attached_logo
blob_id = params[:blob_id]
blob = ActiveStorage::Blob.find_by(id: blob_id)
@@ -79,6 +79,9 @@ class Api::V1::Widget::BaseController < ApplicationController
sender: @contact,
content: permitted_params[:message][:content],
inbox_id: conversation.inbox_id,
content_attributes: {
in_reply_to: permitted_params[:message][:reply_to]
},
echo_id: permitted_params[:message][:echo_id],
message_type: :incoming
}
@@ -1,5 +1,6 @@
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
include Events::Types
before_action :render_not_found_if_empty, only: [:toggle_typing, :toggle_status, :set_custom_attributes, :destroy_custom_attributes]
def index
@conversation = conversation
@@ -27,6 +28,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
conversation.contact_last_seen_at = DateTime.now.utc
conversation.save!
::Conversations::UpdateMessageStatusJob.perform_later(conversation.id, conversation.contact_last_seen_at)
head :ok
end
@@ -41,8 +43,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
end
def toggle_typing
head :ok && return if conversation.nil?
case permitted_params[:typing_status]
when 'on'
trigger_typing_event(CONVERSATION_TYPING_ON)
@@ -54,8 +54,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
end
def toggle_status
return head :not_found if conversation.nil?
return head :forbidden unless @web_widget.end_conversation?
unless conversation.resolved?
@@ -81,6 +79,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: conversation, user: @contact)
end
def render_not_found_if_empty
return head :not_found if conversation.nil?
end
def permitted_params
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email, :phone_number],
message: [:content, :referer_url, :timestamp, :echo_id],
@@ -64,7 +64,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
def permitted_params
# timestamp parameter is used in create conversation method
params.permit(:id, :before, :after, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
params.permit(:id, :before, :after, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id, :reply_to])
end
def set_message
@@ -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!
@@ -9,13 +9,15 @@ class Platform::Api::V1::AgentBotsController < PlatformController
def show; end
def create
@resource = AgentBot.new(agent_bot_params)
@resource = AgentBot.new(agent_bot_params.except(:avatar_url))
@resource.save!
process_avatar_from_url
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
end
def update
@resource.update!(agent_bot_params)
@resource.update!(agent_bot_params.except(:avatar_url))
process_avatar_from_url
end
def destroy
@@ -23,6 +25,11 @@ class Platform::Api::V1::AgentBotsController < PlatformController
head :ok
end
def avatar
@resource.avatar.purge if @resource.avatar.attached?
@resource
end
private
def set_resource
@@ -30,6 +37,10 @@ class Platform::Api::V1::AgentBotsController < PlatformController
end
def agent_bot_params
params.permit(:name, :description, :account_id, :outgoing_url)
params.permit(:name, :description, :account_id, :outgoing_url, :avatar, :avatar_url)
end
def process_avatar_from_url
::Avatar::AvatarFromUrlJob.perform_later(@resource, params[:avatar_url]) if params[:avatar_url].present?
end
end
@@ -23,6 +23,7 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
def update_last_seen
@conversation.contact_last_seen_at = DateTime.now.utc
@conversation.save!
::Conversations::UpdateMessageStatusJob.perform_later(@conversation.id, @conversation.contact_last_seen_at)
head :ok
end
@@ -39,10 +39,6 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
)
end
def portal
@portal ||= Portal.find_by!(slug: permitted_params[:slug], archived: false)
end
def list_params
params.permit(:query, :locale, :sort, :status)
end
@@ -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
@@ -11,11 +12,11 @@ class Public::Api::V1::Portals::BaseController < PublicController
end
def set_color_scheme
@theme = if %w[dark light].include?(params[:theme])
params[:theme]
else
''
end
@theme_from_params = params[:theme] if %w[dark light].include?(params[:theme])
end
def portal
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
end
def set_locale(&)
@@ -40,6 +41,8 @@ class Public::Api::V1::Portals::BaseController < PublicController
def switch_locale_with_article(&)
article = Article.find_by(slug: params[:article_slug])
Rails.logger.info "Article: not found for slug: #{params[:article_slug]}"
render_404 && return if article.blank?
@locale = if article.category.present?
article.category.locale
@@ -53,4 +56,13 @@ class Public::Api::V1::Portals::BaseController < PublicController
def allow_iframe_requests
response.headers.delete('X-Frame-Options') if @is_plain_layout_enabled
end
def render_404
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
@@ -13,10 +13,9 @@ class Public::Api::V1::Portals::CategoriesController < Public::Api::V1::Portals:
private
def set_category
@category = @portal.categories.find_by!(locale: params[:locale], slug: params[:category_slug])
end
@category = @portal.categories.find_by(locale: params[:locale], slug: params[:category_slug])
def portal
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
Rails.logger.info "Category: not found for slug: #{params[:category_slug]}"
render_404 && return if @category.blank?
end
end
@@ -1,21 +1,41 @@
class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
before_action :set_config
before_action :allowed_configs
def show
@allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET]
# ref: https://github.com/rubocop/rubocop/issues/7767
# rubocop:disable Style/HashTransformValues
@fb_config = InstallationConfig.where(name: @allowed_configs)
.pluck(:name, :serialized_value)
.map { |name, serialized_value| [name, serialized_value['value']] }
.to_h
@app_config = InstallationConfig.where(name: @allowed_configs)
.pluck(:name, :serialized_value)
.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
params['app_config'].each do |key, value|
next unless @allowed_configs.include?(key)
i = InstallationConfig.where(name: key).first_or_create(value: value, locked: false)
i.value = value
i.save!
end
redirect_to super_admin_app_config_url
# rubocop:disable Rails/I18nLocaleTexts
redirect_to super_admin_settings_path, notice: 'App Configs updated successfully'
# rubocop:enable Rails/I18nLocaleTexts
end
private
def set_config
@config = params[:config]
end
def allowed_configs
@allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET]
end
end
SuperAdmin::AppConfigsController.prepend_mod_with('SuperAdmin::AppConfigsController')
@@ -20,4 +20,13 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController
params.fetch(resource_name, {}).fetch(:direction, 'desc')
)
end
private
def invalid_action_perfomed
# rubocop:disable Rails/I18nLocaleTexts
flash[:error] = 'Invalid action performed'
# rubocop:enable Rails/I18nLocaleTexts
redirect_back(fallback_location: root_path)
end
end
@@ -27,7 +27,8 @@ class SuperAdmin::Devise::SessionsController < Devise::SessionsController
true
rescue StandardError => e
@error_message = e.message
Rails.logger.error e.message
@error_message = 'Invalid credentials. Please try again.'
false
end
end
@@ -1,4 +1,5 @@
class SuperAdmin::InstallationConfigsController < SuperAdmin::ApplicationController
rescue_from ActiveRecord::RecordNotUnique, :with => :invalid_action_perfomed
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
@@ -0,0 +1,10 @@
class SuperAdmin::SettingsController < SuperAdmin::ApplicationController
def show; end
def refresh
Internal::CheckNewVersionsJob.perform_now
# rubocop:disable Rails/I18nLocaleTexts
redirect_to super_admin_settings_path, notice: 'Instance status refreshed'
# rubocop:enable Rails/I18nLocaleTexts
end
end
@@ -0,0 +1,21 @@
class Twilio::DeliveryStatusController < ApplicationController
def create
::Twilio::DeliveryStatusService.new(params: permitted_params).perform
head :no_content
end
private
def permitted_params
params.permit(
:AccountSid,
:From,
:MessageSid,
:MessagingServiceSid,
:MessageStatus,
:ErrorCode,
:ErrorMessage
)
end
end
-4
View File
@@ -30,11 +30,7 @@ class AccessTokenDashboard < Administrate::BaseDashboard
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
owner
id
token
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
+1
View File
@@ -46,6 +46,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
name
description
outgoing_url
access_token
].freeze
# FORM_ATTRIBUTES
+1
View File
@@ -32,6 +32,7 @@ class PlatformAppDashboard < Administrate::BaseDashboard
name
created_at
updated_at
access_token
].freeze
# FORM_ATTRIBUTES
+3 -1
View File
@@ -36,7 +36,8 @@ class UserDashboard < Administrate::BaseDashboard
updated_at: Field::DateTime,
pubsub_token: Field::String,
type: Field::Select.with_options(collection: [nil, 'SuperAdmin']),
accounts: CountField
accounts: CountField,
access_token: Field::HasOne
}.freeze
# COLLECTION_ATTRIBUTES
@@ -67,6 +68,7 @@ class UserDashboard < Administrate::BaseDashboard
updated_at
confirmed_at
account_users
access_token
].freeze
# FORM_ATTRIBUTES
+5
View File
@@ -18,4 +18,9 @@ class ContactDrop < BaseDrop
def last_name
@obj.try(:name).try(:split).try(:last).try(:capitalize) if @obj.try(:name).try(:split).try(:size) > 1
end
def custom_attribute
custom_attributes = @obj.try(:custom_attributes) || {}
custom_attributes.transform_keys(&:to_s)
end
end
+5
View File
@@ -19,6 +19,11 @@ class ConversationDrop < BaseDrop
end
end
def custom_attribute
custom_attributes = @obj.try(:custom_attributes) || {}
custom_attributes.transform_keys(&:to_s)
end
private
def message_sender_name(sender)
+17 -8
View File
@@ -3,13 +3,21 @@ class ConversationFinder
DEFAULT_STATUS = 'open'.freeze
SORT_OPTIONS = {
latest: 'latest',
sort_on_created_at: 'sort_on_created_at',
last_user_message_at: 'last_user_message_at',
sort_on_priority: 'sort_on_priority',
sort_on_waiting_since: 'sort_on_waiting_since'
}.with_indifferent_access
'last_activity_at_asc' => %w[sort_on_last_activity_at asc],
'last_activity_at_desc' => %w[sort_on_last_activity_at desc],
'created_at_asc' => %w[sort_on_created_at asc],
'created_at_desc' => %w[sort_on_created_at desc],
'priority_asc' => %w[sort_on_priority asc],
'priority_desc' => %w[sort_on_priority desc],
'waiting_since_asc' => %w[sort_on_waiting_since asc],
'waiting_since_desc' => %w[sort_on_waiting_since desc],
# To be removed in v3.5.0
'latest' => %w[sort_on_last_activity_at desc],
'sort_on_created_at' => %w[sort_on_created_at asc],
'sort_on_priority' => %w[sort_on_priority desc],
'sort_on_waiting_since' => %w[sort_on_waiting_since asc]
}.with_indifferent_access
# assumptions
# inbox_id if not given, take from all conversations, else specific to inbox
# assignee_type if not given, take 'all'
@@ -159,7 +167,8 @@ class ConversationFinder
@conversations = @conversations.includes(
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
)
sort_by = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['latest']
@conversations.send(sort_by).page(current_page)
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
@conversations.send(sort_by, sort_order).page(current_page).per(ENV.fetch('CONVERSATION_RESULTS_PER_PAGE', '25').to_i)
end
end
+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
+63 -3
View File
@@ -1,11 +1,71 @@
module PortalHelper
def generate_portal_bg_color(portal_color, theme)
base_color = theme == 'dark' ? 'black' : 'white'
"color-mix(in srgb, #{portal_color} 10%, #{base_color})"
"color-mix(in srgb, #{portal_color} 20%, #{base_color})"
end
def generate_portal_bg(portal_color, theme)
bg_image = theme == 'dark' ? 'grid_dark.svg' : 'grid.svg'
"background: url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}"
bg_image = theme == 'dark' ? 'hexagon-dark.svg' : 'hexagon-light.svg'
"url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}"
end
def generate_gradient_to_bottom(theme)
base_color = theme == 'dark' ? '#151718' : 'white'
"linear-gradient(to bottom, transparent, #{base_color})"
end
def generate_portal_hover_color(portal_color, theme)
base_color = theme == 'dark' ? '#1B1B1B' : '#F9F9F9'
"color-mix(in srgb, #{portal_color} 5%, #{base_color})"
end
def language_name(locale)
language_map = YAML.load_file(Rails.root.join('config/languages/language_map.yml'))
language_map[locale] || locale
end
def theme_query_string(theme)
theme.present? && theme != 'system' ? "?theme=#{theme}" : ''
end
def generate_home_link(portal_slug, portal_locale, theme, is_plain_layout_enabled)
if is_plain_layout_enabled
"/hc/#{portal_slug}/#{portal_locale}#{theme_query_string(theme)}"
else
"/hc/#{portal_slug}/#{portal_locale}"
end
end
def generate_category_link(params)
portal_slug = params[:portal_slug]
category_locale = params[:category_locale]
category_slug = params[:category_slug]
theme = params[:theme]
is_plain_layout_enabled = params[:is_plain_layout_enabled]
if is_plain_layout_enabled
"/hc/#{portal_slug}/#{category_locale}/categories/#{category_slug}#{theme_query_string(theme)}"
else
"/hc/#{portal_slug}/#{category_locale}/categories/#{category_slug}"
end
end
def generate_article_link(portal_slug, article_slug, theme, is_plain_layout_enabled)
if is_plain_layout_enabled
"/hc/#{portal_slug}/articles/#{article_slug}#{theme_query_string(theme)}"
else
"/hc/#{portal_slug}/articles/#{article_slug}"
end
end
def render_category_content(content)
ChatwootMarkdownRenderer.new(content).render_markdown_to_plain_text
end
def thumbnail_bg_color(username)
colors = ['#6D95BA', '#A4C3C3', '#E19191']
return colors.sample if username.blank?
colors[username.length % colors.size]
end
end
+2 -2
View File
@@ -1,13 +1,13 @@
<template>
<div
v-if="!authUIFlags.isFetching"
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
id="app"
class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
:class="{ 'app-rtl--wrapper': isRTLView }"
:dir="isRTLView ? 'rtl' : 'ltr'"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
<template v-if="currentAccountId">
<payment-pending-banner />
<upgrade-banner />
</template>
+5
View File
@@ -84,6 +84,11 @@ export default {
return axios.delete(endPoints('deleteAvatar').url);
},
resetPassword({ email }) {
const urlData = endPoints('resetPassword');
return axios.post(urlData.url, { email });
},
setActiveAccount({ accountId }) {
const urlData = endPoints('setActiveAccount');
return axios.put(urlData.url, {
@@ -1,6 +1,7 @@
/* global axios */
import PortalsAPI from './portals';
import { getArticleSearchURL } from 'dashboard/helper/URLHelper.js';
class ArticlesAPI extends PortalsAPI {
constructor() {
@@ -12,14 +13,31 @@ class ArticlesAPI extends PortalsAPI {
portalSlug,
locale,
status,
author_id,
category_slug,
authorId,
categorySlug,
sort,
}) {
let baseUrl = `${this.url}/${portalSlug}/articles?page=${pageNumber}&locale=${locale}`;
if (status !== undefined) baseUrl += `&status=${status}`;
if (author_id) baseUrl += `&author_id=${author_id}`;
if (category_slug) baseUrl += `&category_slug=${category_slug}`;
return axios.get(baseUrl);
const url = getArticleSearchURL({
pageNumber,
portalSlug,
locale,
status,
authorId,
categorySlug,
sort,
host: this.url,
});
return axios.get(url);
}
searchArticles({ portalSlug, query }) {
const url = getArticleSearchURL({
portalSlug,
query,
host: this.url,
});
return axios.get(url);
}
getArticle({ id, portalSlug }) {
@@ -17,6 +17,10 @@ class PortalsAPI extends ApiClient {
deletePortal(portalSlug) {
return axios.delete(`${this.url}/${portalSlug}`);
}
deleteLogo(portalSlug) {
return axios.delete(`${this.url}/${portalSlug}/logo`);
}
}
export default PortalsAPI;
@@ -26,9 +26,13 @@ export const buildCreatePayload = ({
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
payload.append('bcc_emails', bccEmails);
if (toEmails) {
payload.append('to_emails', toEmails);
}
if (contentAttributes) {
payload.append('content_attributes', JSON.stringify(contentAttributes));
}
} else {
payload = {
content: message,
@@ -82,6 +86,12 @@ class MessageApi extends ApiClient {
return axios.delete(`${this.url}/${conversationID}/messages/${messageId}`);
}
retry(conversationID, messageId) {
return axios.post(
`${this.url}/${conversationID}/messages/${messageId}/retry`
);
}
getPreviousMessages({ conversationId, after, before }) {
const params = { before };
if (after && Number(after) !== Number(before)) {
@@ -34,7 +34,7 @@ describe('#PortalAPI', () => {
portalSlug: 'room-rental',
locale: 'en-US',
status: 'published',
author_id: '1',
authorId: '1',
});
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/portals/room-rental/articles?page=1&locale=en-US&status=published&author_id=1'
@@ -85,6 +85,33 @@ describe('#PortalAPI', () => {
window.axios = originalAxios;
});
it('#searchArticles', () => {
articlesAPI.searchArticles({
query: 'test',
portalSlug: 'room-rental',
});
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/portals/room-rental/articles?query=test'
);
});
});
describe('API calls', () => {
const originalAxios = window.axios;
const axiosMock = {
post: jest.fn(() => Promise.resolve()),
get: jest.fn(() => Promise.resolve()),
patch: jest.fn(() => Promise.resolve()),
delete: jest.fn(() => Promise.resolve()),
};
beforeEach(() => {
window.axios = axiosMock;
});
afterEach(() => {
window.axios = originalAxios;
});
it('#updateArticle', () => {
articlesAPI.updateArticle({
articleId: 1,
@@ -50,6 +50,7 @@ describe('#ConversationAPI', () => {
message: 'test content',
echoId: 12,
isPrivate: true,
contentAttributes: { in_reply_to: 12 },
files: [new Blob(['test-content'], { type: 'application/pdf' })],
});
expect(formPayload).toBeInstanceOf(FormData);
@@ -57,6 +58,10 @@ describe('#ConversationAPI', () => {
expect(formPayload.get('echo_id')).toEqual('12');
expect(formPayload.get('private')).toEqual('true');
expect(formPayload.get('cc_emails')).toEqual('');
expect(formPayload.get('bcc_emails')).toEqual('');
expect(formPayload.get('content_attributes')).toEqual(
'{"in_reply_to":12}'
);
});
it('builds object payload if file is not available', () => {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

@@ -1,8 +1,4 @@
@import '~vue2-datepicker/scss/index';
.mx-datepicker-popup {
@apply z-[99999];
}
@import 'vue2-datepicker/scss/index';
.date-picker {
&.no-margin {
@@ -29,10 +25,52 @@
.mx-input[readonly] {
@apply bg-white dark:bg-slate-900 cursor-pointer;
}
.mx-icon-calendar {
@apply dark:text-slate-500;
}
}
.mx-calendar-content .cell:hover {
@apply bg-slate-75 dark:bg-slate-700 text-slate-900 dark:text-slate-100;
.mx-datepicker-main {
@apply border-0 bg-white dark:bg-slate-800;
.cell {
&.disabled {
@apply bg-slate-25 dark:bg-slate-900 text-slate-200 dark:text-slate-300;
}
&:hover,
&.hover-in-range,
&.in-range {
@apply bg-slate-75 dark:bg-slate-700 text-slate-900 dark:text-slate-100;
}
}
.mx-time {
@apply border-0 bg-white dark:bg-slate-800;
.mx-time-header {
@apply border-0;
}
.mx-time-item {
&.disabled {
@apply bg-slate-25 dark:bg-slate-900;
}
&:hover {
@apply bg-slate-75 dark:bg-slate-700;
}
}
}
.today {
@apply font-semibold;
}
}
.mx-datepicker-popup {
@apply z-[99999];
}
.mx-datepicker-inline {
@@ -41,24 +79,4 @@
.mx-calendar {
@apply w-full;
}
.cell.disabled {
@apply bg-slate-25 dark:bg-slate-900 text-slate-200 dark:text-slate-300;
}
.mx-time-item.disabled {
@apply bg-slate-25 dark:bg-slate-900;
}
.today {
@apply font-semibold;
}
.mx-datepicker-main {
@apply border-0 bg-white dark:bg-slate-800;
}
.mx-time-header {
@apply border-0;
}
}
@@ -1,4 +1,4 @@
@import '~dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/variables';
.formulate-input {
.formulate-input-errors {
@@ -16,3 +16,23 @@
width: 100%;
}
}
.integration-hooks {
.formulate-input[data-type='checkbox'] {
.formulate-input-wrapper {
@apply flex;
.formulate-input-element {
@apply pr-2;
input {
@apply mb-0;
}
}
}
.formulate-input-element-decorator {
@apply hidden;
}
}
}
@@ -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';
@@ -1,39 +1,8 @@
@import '../variables';
@import 'shared/assets/fonts/inter';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
.superadmin-body {
background: var(--color-background);
.hero--title {
font-size: var(--font-size-mega);
font-weight: var(--font-weight-light);
margin-top: var(--space-large);
}
.update-subscription--checkbox {
display: flex;
input {
line-height: 1.5;
margin-right: var(--space-one);
margin-top: var(--space-smaller);
}
label {
font-size: var(--font-size-small);
line-height: 1.5;
margin-bottom: var(--space-normal);
}
}
body {
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
}
.alert-box {
background-color: var(--r-500);
border-radius: 5px;
color: var(--color-white);
font-size: 14px;
margin-bottom: 14px;
padding: 10px;
text-align: center;
}
@@ -1,3 +0,0 @@
@import 'shared/assets/fonts/plus-jakarta';
@import '../variables';
@import '~shared/assets/stylesheets/ionicons';
@@ -34,26 +34,26 @@
}
&.hollow {
@apply border border-slate-200 dark:border-slate-600 text-woot-700 dark:text-woot-100 hover:bg-woot-50 dark:hover:bg-woot-900;
@apply border border-woot-500 dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900;
&.secondary {
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
}
&.success {
@apply text-green-700 dark:text-green-100 hover:bg-green-50 dark:hover:bg-green-800;
@apply text-green-700 dark:text-green-400 border-green-100 dark:border-green-600 hover:bg-green-50 dark:hover:bg-green-800;
}
&.alert {
@apply text-red-700 dark:text-red-100 hover:bg-red-50 dark:hover:bg-red-800;
@apply text-red-700 dark:text-red-400 border-red-100 dark:border-red-600 hover:bg-red-50 dark:hover:bg-red-800;
}
&.warning {
@apply text-yellow-700 dark:text-yellow-100 hover:bg-yellow-50 dark:hover:bg-yellow-800;
@apply text-yellow-600 dark:text-yellow-600 border-yellow-600 dark:border-yellow-700 hover:bg-yellow-50 dark:hover:bg-yellow-800;
}
&:hover {
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
@apply bg-woot-75 dark:bg-woot-800 border-slate-100 dark:border-woot-600 dark:text-woot-400;
&.secondary {
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
@@ -68,7 +68,7 @@
}
&.warning {
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-700;
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-500;
}
}
}
@@ -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;
@@ -152,6 +156,10 @@
&.is-image {
@apply rounded-lg;
.message__mail-head {
@apply px-4 py-2;
}
}
}
@@ -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,11 +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;
}
.modal--close {
@apply absolute right-2 rtl:right-[unset] rtl:left-2 top-2;
@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 {
@@ -49,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 {
+114 -48
View File
@@ -126,49 +126,33 @@
@assign-team="onAssignTeamsForBulk"
/>
<div
ref="activeConversation"
ref="conversationList"
class="conversations-list flex-1"
:class="{ 'overflow-hidden': isContextMenuOpen }"
>
<div>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
</div>
<div v-if="chatListLoading" class="text-center">
<span class="spinner mt-4 mb-4" />
</div>
<woot-button
v-if="!hasCurrentPageEndReached && !chatListLoading"
variant="clear"
size="expanded"
class="load-more--button"
@click="loadMoreConversations"
<virtual-list
ref="conversationVirtualList"
:data-key="'id'"
:data-sources="conversationList"
:data-component="itemComponent"
:extra-props="virtualListExtraProps"
class="w-full overflow-auto h-full"
footer-tag="div"
>
{{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }}
</woot-button>
<p v-if="showEndOfListMessage" class="text-center text-muted p-4">
{{ $t('CHAT_LIST.EOF') }}
</p>
<template #footer>
<div v-if="chatListLoading" class="text-center">
<span class="spinner mt-4 mb-4" />
</div>
<p v-if="showEndOfListMessage" class="text-center text-muted p-4">
{{ $t('CHAT_LIST.EOF') }}
</p>
<intersection-observer
v-if="!showEndOfListMessage && !chatListLoading"
:options="infiniteLoaderOptions"
@observed="loadMoreConversations"
/>
</template>
</virtual-list>
</div>
<woot-modal
:show.sync="showAdvancedFilters"
@@ -191,11 +175,12 @@
<script>
import { mapGetters } from 'vuex';
import VirtualList from 'vue-virtual-scroll-list';
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
import ConversationCard from './widgets/conversation/ConversationCard.vue';
import ConversationItem from './ConversationItem.vue';
import timeMixin from '../mixins/time';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import conversationMixin from '../mixins/conversations';
@@ -207,6 +192,7 @@ import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCust
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
import alertMixin from 'shared/mixins/alertMixin';
import filterMixin from 'shared/mixins/filterMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import countries from 'shared/constants/countries';
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
@@ -221,16 +207,20 @@ import {
isOnUnattendedView,
} from '../store/modules/conversations/helpers/actionHelpers';
import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events';
import IntersectionObserver from './IntersectionObserver.vue';
export default {
components: {
AddCustomViews,
ChatTypeTabs,
ConversationCard,
// eslint-disable-next-line vue/no-unused-components
ConversationItem,
ConversationAdvancedFilter,
DeleteCustomViews,
ConversationBulkActions,
ConversationBasicFilter,
IntersectionObserver,
VirtualList,
},
mixins: [
timeMixin,
@@ -238,7 +228,22 @@ export default {
eventListenerMixins,
alertMixin,
filterMixin,
uiSettingsMixin,
],
provide() {
return {
// Actions to be performed on virtual list item and context menu.
selectConversation: this.selectConversation,
deSelectConversation: this.deSelectConversation,
assignAgent: this.onAssignAgent,
assignTeam: this.onAssignTeam,
assignLabels: this.onAssignLabels,
updateConversationStatus: this.toggleConversationStatus,
toggleContextMenu: this.onContextMenuToggle,
markAsUnread: this.markAsUnread,
assignPriority: this.assignPriority,
};
},
props: {
conversationInbox: {
type: [String, Number],
@@ -273,7 +278,7 @@ export default {
return {
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
activeStatus: wootConstants.STATUS_TYPE.OPEN,
activeSortBy: wootConstants.SORT_BY_TYPE.LATEST,
activeSortBy: wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC,
showAdvancedFilters: false,
advancedFilterTypes: advancedFilterTypes.map(filter => ({
...filter,
@@ -289,6 +294,21 @@ export default {
selectedInboxes: [],
isContextMenuOpen: false,
appliedFilter: [],
infiniteLoaderOptions: {
root: this.$refs.conversationList,
rootMargin: '100px 0px 100px 0px',
},
itemComponent: ConversationItem,
// virtualListExtraProps is to pass the props to the conversationItem component.
virtualListExtraProps: {
label: this.label,
teamId: this.teamId,
foldersId: this.foldersId,
conversationType: this.conversationType,
showAssignee: false,
isConversationSelected: this.isConversationSelected,
},
};
},
computed: {
@@ -408,8 +428,12 @@ export default {
},
conversationListPagination() {
const conversationsPerPage = 25;
const hasChatsOnView =
this.chatsOnView &&
Array.isArray(this.chatsOnView) &&
!this.chatsOnView.length;
const isNoFiltersOrFoldersAndChatListNotEmpty =
!this.hasAppliedFiltersOrActiveFolders && this.chatsOnView !== [];
!this.hasAppliedFiltersOrActiveFolders && hasChatsOnView;
const isUnderPerPage =
this.chatsOnView.length < conversationsPerPage &&
this.activeAssigneeTabCount < conversationsPerPage &&
@@ -495,6 +519,9 @@ export default {
},
},
watch: {
teamId() {
this.updateVirtualListProps('teamId', this.teamId);
},
activeTeam() {
this.resetAndFetchData();
},
@@ -503,18 +530,25 @@ export default {
},
label() {
this.resetAndFetchData();
this.updateVirtualListProps('label', this.label);
},
conversationType() {
this.resetAndFetchData();
this.updateVirtualListProps('conversationType', this.conversationType);
},
activeFolder() {
this.resetAndFetchData();
this.updateVirtualListProps('foldersId', this.foldersId);
},
chatLists() {
this.chatsOnView = this.conversationList;
},
showAssigneeInConversationCard(newVal) {
this.updateVirtualListProps('showAssignee', newVal);
},
},
mounted() {
this.setFiltersFromUISettings();
this.$store.dispatch('setChatStatusFilter', this.activeStatus);
this.$store.dispatch('setChatSortFilter', this.activeSortBy);
this.resetAndFetchData();
@@ -528,6 +562,12 @@ export default {
});
},
methods: {
updateVirtualListProps(key, value) {
this.virtualListExtraProps = {
...this.virtualListExtraProps,
[key]: value,
};
},
onApplyFilter(payload) {
this.resetBulkActions();
this.foldersQuery = filterQueryGenerator(payload);
@@ -544,6 +584,15 @@ export default {
this.$store.dispatch('customViews/update', payloadData);
this.closeAdvanceFiltersModal();
},
setFiltersFromUISettings() {
const { conversations_filter_by: filterBy = {} } = this.uiSettings;
const { status, order_by: orderBy } = filterBy;
this.activeStatus = status || wootConstants.STATUS_TYPE.OPEN;
this.activeSortBy =
Object.keys(wootConstants.SORT_BY_TYPE).find(
sortField => sortField === orderBy
) || wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC;
},
onClickOpenAddFoldersModal() {
this.showAddFoldersModal = true;
},
@@ -622,10 +671,10 @@ export default {
);
},
getKeyboardListenerParams() {
const allConversations = this.$refs.activeConversation.querySelectorAll(
const allConversations = this.$refs.conversationList.querySelectorAll(
'div.conversations-list div.conversation'
);
const activeConversation = this.$refs.activeConversation.querySelector(
const activeConversation = this.$refs.conversationList.querySelector(
'div.conversations-list div.conversation.active'
);
const activeConversationIndex = [...allConversations].indexOf(
@@ -681,9 +730,12 @@ export default {
fetchConversations() {
this.$store
.dispatch('fetchAllConversations', this.conversationFilters)
.then(() => this.$emit('conversation-load'));
.then(this.emitConversationLoaded);
},
loadMoreConversations() {
if (this.hasCurrentPageEndReached || this.chatListLoading) {
return;
}
if (!this.hasAppliedFiltersOrActiveFolders) {
this.fetchConversations();
}
@@ -702,7 +754,7 @@ export default {
queryData: filterQueryGenerator(payload),
page,
})
.then(() => this.$emit('conversation-load'));
.then(this.emitConversationLoaded);
this.showAdvancedFilters = false;
},
fetchSavedFilteredConversations(payload) {
@@ -712,7 +764,7 @@ export default {
queryData: payload,
page,
})
.then(() => this.$emit('conversation-load'));
.then(this.emitConversationLoaded);
},
updateAssigneeTab(selectedTab) {
if (this.activeAssigneeTab !== selectedTab) {
@@ -724,6 +776,20 @@ export default {
}
}
},
emitConversationLoaded() {
this.$emit('conversation-load');
this.$nextTick(() => {
// Addressing a known issue in the virtual list library where dynamically added items
// might not render correctly. This workaround involves a slight manual adjustment
// to the scroll position, triggering the list to refresh its rendering.
const virtualList = this.$refs.conversationVirtualList;
const scrollToOffset = virtualList?.scrollToOffset;
const currentOffset = virtualList?.getOffset() || 0;
if (scrollToOffset) {
scrollToOffset(currentOffset + 1);
}
});
},
resetBulkActions() {
this.selectedConversations = [];
this.selectedInboxes = [];
@@ -0,0 +1,72 @@
<template>
<conversation-card
:key="source.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="source"
:conversation-type="conversationType"
:selected="isConversationSelected(source.id)"
:show-assignee="showAssignee"
:enable-context-menu="true"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="assignAgent"
@assign-team="assignTeam"
@assign-label="assignLabels"
@update-conversation-status="updateConversationStatus"
@context-menu-toggle="toggleContextMenu"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
</template>
<script>
import ConversationCard from './widgets/conversation/ConversationCard.vue';
export default {
components: {
ConversationCard,
},
inject: [
'selectConversation',
'deSelectConversation',
'assignAgent',
'assignTeam',
'assignLabels',
'updateConversationStatus',
'toggleContextMenu',
'markAsUnread',
'assignPriority',
],
props: {
source: {
type: Object,
required: true,
},
teamId: {
type: [String, Number],
default: 0,
},
label: {
type: String,
default: '',
},
conversationType: {
type: String,
default: '',
},
foldersId: {
type: [String, Number],
default: 0,
},
isConversationSelected: {
type: Function,
default: () => {},
},
showAssignee: {
type: Boolean,
default: false,
},
},
};
</script>
@@ -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;
}
@@ -0,0 +1,34 @@
<template>
<div ref="observedElement" class="h-6 w-full" />
</template>
<script>
export default {
props: {
options: {
type: Object,
default: () => ({ root: document, rootMargin: '100px 0 100px 0)' }),
},
},
mounted() {
this.intersectionObserver = null;
this.registerInfiniteLoader();
},
beforeDestroy() {
this.unobserveInfiniteLoadObserver();
},
methods: {
registerInfiniteLoader() {
this.intersectionObserver = new IntersectionObserver(entries => {
if (entries && entries[0].isIntersecting) {
this.$emit('observed');
}
}, this.options);
this.intersectionObserver.observe(this.$refs.observedElement);
},
unobserveInfiniteLoadObserver() {
this.intersectionObserver.unobserve(this.$refs.observedElement);
},
},
};
</script>
+23 -5
View File
@@ -4,15 +4,19 @@
v-if="show"
:class="modalClassName"
transition="modal"
@click="onBackDropClick"
@mousedown="handleMouseDown"
>
<div :class="modalContainerClassName" @click.stop>
<div
:class="modalContainerClassName"
@mouse.stop
@mousedown="event => event.stopPropagation()"
>
<woot-button
v-if="showCloseButton"
color-scheme="secondary"
icon="dismiss"
variant="clear"
class="modal--close"
class="absolute ltr:right-2 rtl:left-2 top-2 z-10"
@click="close"
/>
<slot />
@@ -50,6 +54,11 @@ export default {
default: '',
},
},
data() {
return {
mousedDownOnBackdrop: false,
};
},
computed: {
modalContainerClassName() {
let className =
@@ -77,13 +86,22 @@ export default {
this.onClose();
}
});
document.body.addEventListener('mouseup', this.onMouseUp);
},
beforeDestroy() {
document.body.removeEventListener('mouseup', this.onMouseUp);
},
methods: {
handleMouseDown() {
this.mousedDownOnBackdrop = true;
},
close() {
this.onClose();
},
onBackDropClick() {
if (this.closeOnBackdropClick) {
onMouseUp() {
if (this.mousedDownOnBackdrop) {
this.mousedDownOnBackdrop = false;
this.onClose();
}
},

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