Compare commits

..
Author SHA1 Message Date
Muhsin Keloth 3496c9c16a Merge branch 'fix/slack-attachment-issues' of https://github.com/chatwoot/chatwoot into fix/slack-attachment-issues 2023-08-25 19:01:45 +05:30
Muhsin Keloth 1b17926b90 Revert facebook code 2023-08-25 19:01:40 +05:30
Muhsin Keloth b21e9849ba chore: fix slack attachment issue 2023-08-25 19:01:40 +05:30
Muhsin Keloth 64db949581 Fix slack attachment issues 2023-08-25 19:01:40 +05:30
Muhsin Keloth 8c1c1a5ab2 chore: move lib to services 2023-08-25 19:01:40 +05:30
Sojan JoseandGitHub 6ab964b161 chore: fix flaky spec (#7686)
- fix flaky reporting specs
- fix flaky export spec
2023-08-25 00:11:41 -07:00
Muhsin Keloth 2971d78f8c Revert facebook code 2023-08-25 11:39:46 +05:30
Muhsin Keloth 7f8d8a112d chore: fix slack attachment issue 2023-08-25 10:59:41 +05:30
Shivam MishraandGitHub 5598b4b27e feat: implement mutex for SlackSendJob (#7783) 2023-08-25 11:58:29 +07:00
Sojan JoseandGitHub 64ae9f625a chore: Persist emojis in Contact Import (#7803)
The previous fix would remove emojis in contact data. This change ensures they are persisited

ref: #7787
2023-08-24 14:04:52 -07:00
Pranav Raj SandGitHub 3dd3b7b3aa fix: Rescue InvalidAuth and mark integration as inactive (#7797) 2023-08-24 17:02:24 +05:30
Pranav Raj SandGitHub f451e59aec chore: Update translations (#7796) 2023-08-24 17:02:11 +05:30
paulocrandGitHub c04f67e8a5 fix: Update translation in Spanish (#7770) 2023-08-24 16:24:48 +05:30
Pranav Raj SandGitHub 6ddc99d066 fix: Update the in_reply_to to logic to use processed_mail (#7793) 2023-08-24 16:19:17 +05:30
Sojan JoseandGitHub 4cbb3208e8 chore: Revert gem update (#7792)
- updating redis client caused deployment issues in Heroku, hence reverting to prev version until its resolved
2023-08-24 01:08:34 -07:00
Pranav Raj SandGitHub 5f2a720920 chore: Update translations (#7766) 2023-08-24 13:26:39 +05:30
Sojan JoseandGitHub acb7debd3f chore: Contact import improvements (#7787)
- Ensure existing contact information is updated on data import
- Refactor the existing job to make it more readable
- Fixes issues with import files in the wrong encoding
fixes: #7307
2023-08-23 23:24:47 -07:00
Shivam MishraandGitHub 44f6a9ec3c feat: update lock timeout and retry configuration (#7785) 2023-08-24 12:59:47 +07:00
Shivam MishraandGitHub 24468d71fb feat: update rails (#7786)
Bump up rails
2023-08-23 13:45:55 -07:00
Shivam MishraandGitHub 26ef21a243 feat: locking and retry in FB message parsing (#7701) 2023-08-23 09:48:17 +07:00
Sivin VargheseandGitHub 18235d3fb5 fix: Draft, Articles, Archived are counted wrong in help-center (#7761) 2023-08-21 12:42:10 +05:30
Sivin VargheseandGitHub 55972cc17a fix: Dashboard overflow issue if there is banner (#7748) 2023-08-21 12:22:50 +05:30
2daf1ae827 feat: Event to capture the message signature feature (#7760)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-08-21 13:30:21 +07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Shivam Mishra
22421b5ac8 chore(deps): bump puma from 6.2.2 to 6.3.1 (#7765)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2023-08-21 13:09:20 +07:00
Muhsin KelothandGitHub 0d41853b0b fix: OpenAI CE specs (#7710) 2023-08-21 12:54:35 +07:00
LiamandGitHub 355926897a fix: Fixes error while updating widget builder title (#7756) 2023-08-19 10:57:44 +05:30
Pranav Raj SandGitHub 4101a7b566 chore: Update translations from Crowdin (#7750) 2023-08-18 12:48:50 -07:00
2d31535620 fix: Improve the contrast for the code snippet on the Widget Builder page. (#7758)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-08-18 12:44:30 -07:00
LiamandGitHub c54b469dcb fix: Update the style of the 'Go To' input in the conversations table (#7753) 2023-08-17 13:50:11 -07:00
Pranav Raj SandGitHub 29e8e41443 fix: Delete agent bots without deleting the messages (#7754) 2023-08-17 13:48:49 -07:00
178bc80b25 fix: Update email alerts for Slack integration (#7739)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-08-17 11:46:37 -07:00
Sivin VargheseandGitHub ab039e14fd chore: Improve article list pagination logic (#7737) 2023-08-17 09:32:29 -07:00
LiamandGitHub 961c911c26 fix: Fix the issue where tag agent option is appearing above the profile menu (#7743) 2023-08-17 09:23:26 -07:00
Muhsin KelothandGitHub 12ae49089b feat: Add analytics events for notification feature (#7749) 2023-08-17 09:18:31 -07:00
Jordan BroughandGitHub 996f842882 feat: Allow Twilio::REST::TwilioError errors to raise and prevent Inbox creation (#7379)
This update will mean that errors will roll back the current transaction and the error will be sent back to the frontend and the user will know that the Inbox did not finish setting up successfully.
2023-08-16 15:00:02 -07:00
Sivin VargheseandGitHub 616f702871 fix: Agent name not visible in team page in Dark mode (#7730)
Fixes: https://linear.app/chatwoot/issue/CW-2399/agent-name-not-visible-in-team-page
2023-08-16 14:39:14 -07:00
Pranav Raj SandGitHub 8015490aba chore: Update translations (#7731) 2023-08-16 11:18:14 -07:00
Muhsin Keloth 68205d8fd3 Fix slack attachment issues 2023-08-16 15:47:41 +05:30
Muhsin Keloth d42206e03b chore: move lib to services 2023-08-16 15:47:35 +05:30
Muhsin KelothandGitHub b89c917198 feat: Add CTAs for AI features (#7538) 2023-08-16 08:39:41 +05:30
7b8a3fcae0 feat: auditlog for team and inbox member updates (#7516)
- adds an audit log when an agent is added or removed from a team
- adds an audit log when an agent is added or removed from an inbox

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-08-15 19:55:19 -07:00
Sojan JoseandGitHub 2df83276e0 chore: Ability to disable rack attack on widget endpoints (#7729) 2023-08-15 17:44:25 -07:00
7b68a7639f fix: Set the default value to false for the checkbox custom attribute (#7561)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-15 16:32:06 -07:00
e3b8c1fbb5 fix: Include waiting on agent conversations to unattended view (#7667)
Updating the `unattended` tab to include conversations where the customer responded and is awaiting an agent's response.

Previously it showed only the conversations where the first response was pending.

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-15 15:09:10 -07:00
Pranav Raj SandGitHub 310c127693 chore: Update translations (#7728) 2023-08-15 13:47:38 -07:00
Pranav Raj SandGitHub 37c0f1306c fix: Fix breaking storybook build (#7723) 2023-08-14 20:19:02 -07:00
396ef02b0c feat: Improve the initial screen placeholder (#7708)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-14 17:50:34 -07:00
Pranav Raj SandGitHub ebefb2e201 fix: Consider the emails where in-reply-to header has multiple values (#7715)
- Return the first message id for now to avoid the errors and subsequently missing the message.
- Use .last instead of .first to avoid expensive query.
- Fix array response in response bot.

Fixes:  https://linear.app/chatwoot/issue/CW-2358/activerecordstatementinvalid-pgdatatypemismatch-error-argument-of-and
2023-08-11 17:53:57 -07:00
Pranav Raj SandGitHub 6f09f20991 feat: Update bot typing indicator based on the conversation status (#7714)
- Show the indicator if the last message is incoming and the conversation is in pending status.
- Remove list of articles displayed in the bot response.
2023-08-11 16:41:01 -07:00
cfe86d9c06 fix: Create new slack thread if the thread identifier changes (#7702)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2023-08-11 14:19:49 +05:30
Shivam MishraandGitHub 84240e197a fix: Summary prompt test (#7709) 2023-08-11 13:42:27 +05:30
Pranav Raj SandGitHub bafff90f18 chore: Add account_id scope for conversation.messages (#7707) 2023-08-10 23:02:53 -07:00
Pranav Raj SandGitHub 961d810645 Revert "feat: Show popular articles on widget home" (#7706) 2023-08-10 22:27:51 -07:00
Pranav Raj SandGitHub 1a06bfd69c fix: Force account_id in message filters (#7705) 2023-08-10 21:58:25 -07:00
Pranav Raj SandGitHub 1cac2e86c3 fix: Limit count query to return only 10 values (#7704) 2023-08-10 19:30:02 -07:00
Pranav Raj SandGitHub 04287a0924 fix: Rename channel_list to channels to avoid "no implicit conversion of nil" exception (#7700) 2023-08-09 16:11:51 -07:00
c1c3a62412 fix: Improve reply prompt (#7588)
Co-authored-by: Sojan Jose <sojan@chatwoot.com>
2023-08-09 10:25:37 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b5dec067a1 chore(deps): bump commonmarker from 0.23.9 to 0.23.10 (#7695)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 14:25:42 -07:00
Sivin VargheseandGitHub f96f166196 fix: Default timezone is not working (#7692) 2023-08-08 21:30:29 +05:30
Sivin VargheseandGitHub 9ed701065e fix: Dark mode color fixes (#7693) 2023-08-08 21:07:15 +05:30
Vishnu NarayananandGitHub 180befbd42 feat: add gh action to check logging percentage (#7688) 2023-08-08 15:51:46 +05:30
Pranav Raj SandGitHub 4baa4363f4 fix: Update unread_count being 0 if agent has not seen the conversation (#7690) 2023-08-07 18:50:31 -07:00
Pranav Raj SandGitHub 1c8f41f387 fix: Rename logo-dark -> logo_dark to match the installation config (#7689)
Fix: #7676
2023-08-07 14:30:16 -07:00
7f0ca8b15d fix: Security upgrade rack-mini-profiler from 3.1.0 to 3.1.1 [Snyk] (#7657)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-08-07 14:09:28 -07:00
Sojan JoseandGitHub 4c75bb68f4 chore: Fix OpenAI sentry errors (#7687)
- add check for nil condition
- add logs for openAI
2023-08-07 14:08:26 -07:00
Vishnu NarayananandGitHub ed82eb2932 refactor: remove beta label from automation, bots and macros (#7683)
Fixes: https://linear.app/chatwoot/issue/CW-2334/remove-beta-label-from-everything
2023-08-07 11:15:24 -07:00
Sivin VargheseandGitHub b62166a18a fix: Cannot read properties of undefined (#7682) 2023-08-07 20:49:13 +05:30
bf2b75b281 feat: Add message timestamp to the conversation transcript emails (#7648)
Co-authored-by: Cristian Duta <Cristian.Duta@ti8m.ch>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-04 17:21:14 -07:00
c084ad5a68 [Snyk] Security upgrade lograge from 0.12.0 to 0.13.0 (#7635)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-08-04 16:56:11 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Sojan Jose
dd3e658f19 chore(deps): bump semver from 7.3.5 to 7.5.2 (#7383)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-08-04 16:53:30 -07:00
453d7ad9fc fix: Update options interactive messages formatting in web widget (#7437)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-04 16:52:32 -07:00
Pranav Raj SandGitHub 47b6967dad fix: Update account data seeder to avoid invalid information (#7673)
- The message sender was incorrect. Incoming messages were created under the contact's name, and outgoing messages were created under the user's name instead of the reverse.
- The seed user's email address was incorrect in the message data.
- The Sendmail configuration overrode the Letter Opener config which made it difficult to test the email.
- This PR also fixes an ESLint lint issue on develop.
2023-08-04 16:32:54 -07:00
Sojan JoseandGitHub d06cf27800 chore: Specs for pageCrawler Service (#7632) 2023-08-04 15:44:51 -07:00
Sivin VargheseandGitHub 7f6a3b781f fix: Links are not readable in private notes dark mode (#7671) 2023-08-05 00:10:10 +05:30
fde2b180fe feat: Update design for campaign (#7668)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-08-04 11:39:59 -07:00
Sivin VargheseandGitHub 2a1662c781 fix: Edit custom views folder (#7670) 2023-08-04 13:54:21 +05:30
10d6e9551d feat: Add the option to toggle the dark/light color-scheme (#7662)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-08-03 12:21:45 -07:00
Sivin VargheseandGitHub 69d46f278a feat: Dark mode improvements (#7658) 2023-08-03 12:05:22 -07:00
Muhsin KelothandGitHub 0921a7c817 fix: Enable cmd bar actions for all conversation routes (#7664) 2023-08-03 11:40:03 -07:00
Muhsin KelothandGitHub 2a73f124ca feat: Add analytics event for command bar usage (#7659) 2023-08-02 13:45:26 +05:30
Sojan JoseandGitHub ef33c60652 chore: Bump up node version (#7653)
Bump node version to 16.20.1.
 
ref: #6629
2023-08-01 12:48:02 -07:00
Pranav Raj SandGitHub 3a547de909 fix: Update the default status of the hooks to enabled (#7652)
In the recent Slack integration update, we made changes to how hooks are handled. Now, hooks require an additional check to be enabled. By default, new hooks are created in a disabled state, which might lead to issues with the hooks not being executed as expected.

This migration updates the status attribute of hooks to have 'enabled' as the default value, ensuring that new hooks are enabled by default and can function properly.
2023-08-01 12:14:47 -07:00
Shivam MishraandGitHub 62e9fc1bc5 feat: update color palette [CW-2293] (#7617) 2023-08-01 21:34:10 +05:30
Nithin David ThomasandGitHub e052a061f4 feat: Show popular articles on widget home (#7604) 2023-08-01 21:32:44 +05:30
LiamandGitHub 9efadf8804 fix: Update permitted_attributes to support the latest version of administrate (#7644)
This fixes an issue where permitted_attributes throws an error of ArgumentError (wrong number of arguments (given 1, expected 0)):

This is because we now bring in the latest version of administrate and administrate changes the way permitted_attributes works; it now passes an action parameter to permitted_attributes. This means we need to update permitted_attributes to accept that new parameter.

Fixes: #7592
2023-07-31 11:53:44 -07:00
Muhsin KelothandGitHub c8a40932cc chore: Add June event for AI reply suggest dismissal (#7601) 2023-07-31 13:20:47 +05:30
Pranav Raj SandGitHub 9ddd428935 feat: Update the slack integration-flow to allow users to select the channel (#7637) 2023-07-28 14:50:30 -07:00
Sojan JoseandGitHub 4d8ba0148c fix: Use Display name for campaigns (#7631)
fixes: #4299
2023-07-27 17:58:19 +03:00
65c4ad70e1 refactor: improve logging for channel callback controllers (#7555)
Co-authored-by: Tejaswini Chile <tejaswini@chatwoot.com>
2023-07-27 17:10:39 +05:30
9636478c2a chore: Improve logging for ip look up and audit logs [CW-2145] (#7512)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-07-27 16:48:30 +05:30
Nic HippenmeyerandGitHub 65190422c4 fix: Rename unread_since scope (#7267)
- This renames the unread_since scope to created_since, which more accurately describes what the scope returns.
- The EXTRACT(EPOCH FROM created_at) > (?) clause was also simplified and rewritten as created_at > ?, which is equivalent:
2023-07-27 12:29:21 +03:00
Jordan BroughandGitHub a7bc855486 refactor: Remove deprecated "belongs_to" relationships in Message (#7434)
- Remove the deprecated columns in message model
2023-07-27 11:41:34 +03:00
Shivam MishraandGitHub ff97536095 feat(perf): contact page loading speed (#7629) 2023-07-27 13:32:22 +05:30
Sivin VargheseandGitHub fcf0ba12d0 feat: Dark Mode (PR -12) (#7590) 2023-07-27 12:28:06 +05:30
Shivam MishraandGitHub 2cc82af344 fix: apply event name (#7628) 2023-07-27 12:11:57 +05:30
c00855fce4 fix: Skip sentiment analysis for blank text (#7580)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-07-27 12:11:48 +05:30
2d07577731 fix: [Snyk] Security upgrade rack-attack from 6.6.1 to 6.7.0 (#7625)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-07-27 09:06:56 +03:00
BernieandGitHub 4c98006837 fix: Fix spelling mistakes in the copy (#7627) 2023-07-26 21:48:28 -07:00
Nithin David ThomasandGitHub 89e09857af feat: Add store and API to support articles in widget (#7616) 2023-07-26 14:08:27 -07:00
Shivam MishraandGitHub 12c338364e fix: sentry issues [CW-2222][CW-2232] (#7591) 2023-07-26 23:37:37 +05:30
Sojan JoseandGitHub 846f520ad2 feat: toggle typing and update last seen endpoints for client apis (#7621)
- Add toggle_typing endpoint for client APIs
- Adds update last seen endpoint for client APIs 

Fixes: #7581
2023-07-26 20:40:48 +03:00
Sojan JoseandGitHub 6cbe1ed911 chore: Disable message hooks for conversations without incoming message (#7620)
When using client APIs to create conversations and auto-assignment is turned on, welcome messages were getting triggered. This PR disable the behaviour and ensure template hooks are triggered only if there are incoming messages present. 

Fixes: https://linear.app/chatwoot/issue/CW-2187
2023-07-26 20:38:49 +03:00
LiamandGitHub 6c1ee4d965 fix: Contacts page sort by created_at [CW-2262] (#7584)
Fixes the contacts page sort by created_at.
fixes: #7577
2023-07-26 18:37:30 +03:00
OMAR.AandGitHub 1d8341504a fix: contacts count on filter (#7446)
- Fixes the wrong count shown during the contact filter when contact has multiple labels associated.
2023-07-26 17:31:04 +03:00
Shivam MishraandGitHub f8ae6cd95c fix: no method error when conversation is nil (#7566) 2023-07-26 17:40:41 +05:30
LiamandGitHub 63dd2c5a93 fix: Avoid profile options menu overlapping with message tabs (#7615) 2023-07-25 21:55:57 -07:00
Shivam MishraandSojan 1c47478104 fix: undefined method update_cache_key for nil:NilClass (#7568) 2023-07-25 13:00:34 +03:00
Shivam MishraandSojan 39f14bebbd fix: undefined method first for nil:NilClass (#7567)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-07-25 13:00:21 +03:00
LiamandGitHub 4b27fdf4db fix: Increase the out of office message length to 10_000 (#7583) 2023-07-24 20:29:04 -07:00
Pranav Raj SandGitHub a6a0e78bbe feat: Sort articles based on views (#7599) 2023-07-24 20:27:43 -07:00
703e19304d feat: Components to render articles in widget home (#7596)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-24 16:45:55 -07:00
Pranav Raj SandGitHub fa7bbdb0b3 feat: Add support for plain design help center layout (#7598) 2023-07-25 05:13:52 +05:30
Nithin David ThomasandGitHub d837065545 chore: Update storybook settings to widget stories (#7595) 2023-07-24 16:28:20 -07:00
2a6f3356c9 chore: Show error message if the AI process APIs fails (#7560)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2023-07-24 19:15:34 +05:30
c83105ce4f feat: dismiss label suggestions only for 24 hours (#7579)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2023-07-24 17:51:09 +05:30
Sojan JoseandGitHub 56d0b220f4 chore: Fix response associations for inbox (#7582)
Publish Chatwoot CE docker images / build (push) Waiting to run
- Fix the bug in inbox associations that crept up in previous PR
2023-07-21 20:47:41 +03:00
Sivin VargheseandGitHub 40ec0d109a feat: Dark Mode (#7471) 2023-07-21 22:10:25 +05:30
Sojan JoseandGitHub 480f34803b feat: Response Bot using GPT and Webpage Sources (#7518)
This commit introduces the ability to associate response sources to an inbox, allowing external webpages to be parsed by Chatwoot. The parsed data is converted into embeddings for use with GPT models when managing customer queries.

The implementation relies on the `pgvector` extension for PostgreSQL. Database migrations related to this feature are handled separately by `Features::ResponseBotService`. A future update will integrate these migrations into the default rails migrations, once compatibility with Postgres extensions across all self-hosted installation options is confirmed.

Additionally, a new GitHub action has been added to the CI pipeline to ensure the execution of specs related to this feature.
2023-07-21 18:11:51 +03:00
Sojan JoseandGitHub 30f3928904 chore: bump up version to 3.0.0 (#7574)
Bump the Chatwoot version to 3.0.0
ref: https://github.com/orgs/chatwoot/discussions/7570
2023-07-21 15:02:05 +03:00
Vishnu NarayananandGitHub d188600559 fix: modify exception tracker to log even if sentry configured (#7563)
Right now, if sentry is configured exception won't be logged. This results in the log management tool missing every error captured with ChatwootExceptionTracker. This change logs the exception, even if Sentry is configured or not.

Fixes https://linear.app/chatwoot/issue/CW-2145/improve-logging-info-debug-trace
2023-07-21 11:58:49 +03:00
Vishnu NarayananandGitHub 4828071fc3 feat: add audit trail for channel updates (#7396) 2023-07-21 12:08:19 +05:30
Shivam MishraandGitHub a3d21024a6 feat: add message condition (#7495) 2023-07-21 10:20:53 +05:30
1329 changed files with 31113 additions and 14954 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ defaults: &defaults
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
- image: cimg/postgres:14.1
- image: cimg/postgres:15.3
- image: cimg/redis:6.2.6
environment:
- RAILS_LOG_TO_STDOUT: false
+5
View File
@@ -231,5 +231,10 @@ AZURE_APP_SECRET=
# control the concurrency setting of sidekiq
# SIDEKIQ_CONCURRENCY=10
# AI powered features
## OpenAI key
# OPENAI_API_KEY=
# Sentiment analysis model file path
SENTIMENT_FILE_PATH=
@@ -0,0 +1,55 @@
name: Log Lines Percentage Check
on:
pull_request:
branches:
- develop
jobs:
log_lines_check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Check for log lines and calculate percentage
run: |
# Define the log line pattern
LOG_LINE_PATTERN="Rails\.logger"
# Get the list of changed files in the pull request
CHANGED_FILES=$(git diff --name-only)
# Initialize a flag to track if any files have insufficient log lines
INSUFFICIENT_LOGS=0
for file in $CHANGED_FILES; do
if [[ $file =~ \.rb$ && ! $file =~ _spec\.rb$ ]]; then
# Count the total number of lines in the file
total_lines=$(wc -l < "$file")
# Count the number of log lines in the file
log_lines=$(grep -c "$LOG_LINE_PATTERN" "$file")
# Calculate the percentage of log lines
if [ "$total_lines" -gt 0 ]; then
percentage=$(awk "BEGIN { pc=100*${log_lines}/${total_lines}; i=int(pc); print (pc-i<0.5)?i:i+1 }")
else
percentage=0
fi
# Check if the percentage is less than 5%
if [ "$percentage" -lt 5 ]; then
echo "Error: Log lines percentage is less than 5% ($percentage%) in $file. Please add more log lines using Rails.logger statements."
INSUFFICIENT_LOGS=1
else
echo "Log lines percentage is $percentage% in $file. Code looks good!"
fi
fi
done
# If any files have insufficient log lines, fail the action
if [ "$INSUFFICIENT_LOGS" -eq 1 ]; then
exit 1
fi
+2 -1
View File
@@ -18,11 +18,12 @@ jobs:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10.8
image: postgres:15.3
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ""
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
@@ -0,0 +1,78 @@
# #
# # This workflow will run specs related to response bot
# # This can only be activated in installations Where vector extension is available.
# #
name: Run Response Bot spec
on:
push:
branches:
- develop
- master
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-20.04
services:
postgres:
image: ankane/pgvector
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ""
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
# tmpfs makes DB faster by using RAM
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: yarn
run: yarn install
- name: Create database
run: bundle exec rake db:create
- name: Seed database
run: bundle exec rake db:schema:load
- name: Enable ResponseBotService in installation
run: RAILS_ENV=test bundle exec rails runner "Features::ResponseBotService.new.enable_in_installation"
# Run Response Bot specs
- name: Run backend tests
run: |
bundle exec rspec spec/enterprise/controllers/api/v1/accounts/response_sources_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/inboxes_controller_spec.rb:47 --profile=10 --format documentation
- name: Upload rails log folder
uses: actions/upload-artifact@v3
if: always()
with:
name: rails-log-folder
path: log
+1 -1
View File
@@ -1 +1 @@
14.17.4
16.20.1
+2 -2
View File
@@ -73,7 +73,7 @@ Rails/ApplicationController:
- 'app/controllers/survey/responses_controller.rb'
Rails/FindEach:
Enabled: true
Include:
Include:
- 'app/**/*.rb'
Rails/CompactBlank:
Enabled: false
@@ -87,6 +87,7 @@ Style/ClassAndModuleChildren:
EnforcedStyle: compact
Exclude:
- 'config/application.rb'
- 'config/initializers/monkey_patches/*'
Style/MapToHash:
Enabled: false
Style/HashSyntax:
@@ -188,7 +189,6 @@ RSpec/IndexedLet:
RSpec/NamedSubject:
Enabled: false
# we should bring this down
RSpec/MultipleMemoizedHelpers:
Max: 14
+13 -1
View File
@@ -23,6 +23,18 @@ module.exports = {
},
'@storybook/addon-links',
'@storybook/addon-essentials',
{
/**
* Fix Storybook issue with PostCSS@8
* @see https://github.com/storybookjs/storybook/issues/12668#issuecomment-773958085
*/
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
webpackFinal: config => {
const newConfig = {
@@ -35,7 +47,7 @@ module.exports = {
newConfig.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
include: path.resolve(__dirname, '../app/javascript'),
});
+4
View File
@@ -4,10 +4,12 @@ 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';
import WootUiKit from '../app/javascript/dashboard/components';
import i18n from '../app/javascript/dashboard/i18n';
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer';
import '../app/javascript/dashboard/assets/scss/storybook.scss';
@@ -15,6 +17,8 @@ Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(WootUiKit);
Vue.use(Vuex);
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
Vue.component('multiselect', Multiselect);
Vue.component('fluent-icon', FluentIcon);
+11 -4
View File
@@ -4,7 +4,7 @@ ruby '3.2.2'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~> 7.0.5.1'
gem 'rails', '~> 7.0.7.2'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false
@@ -34,7 +34,7 @@ gem 'commonmarker'
# Validate Data against JSON Schema
gem 'json_schemer'
# Rack middleware for blocking & throttling abusive requests
gem 'rack-attack'
gem 'rack-attack', '>= 6.7.0'
# a utility tool for streaming, flexible and safe downloading of remote files
gem 'down'
# authentication type to fetch and send mail over oauth2.0
@@ -153,7 +153,7 @@ gem 'stripe'
gem 'faker'
# Include logrange conditionally in intializer using env variable
gem 'lograge', '~> 0.12.0', require: false
gem 'lograge', '~> 0.13.0', require: false
# worked with microsoft refresh token
gem 'omniauth-oauth2'
@@ -165,6 +165,13 @@ gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
## Gems for reponse bot
# adds cosine similarity to postgres using vector extension
gem 'neighbor'
gem 'pgvector'
# Convert Website HTML to Markdown
gem 'reverse_markdown'
# Sentiment analysis
gem 'informers'
@@ -190,7 +197,7 @@ group :development do
gem 'squasher'
# profiling
gem 'rack-mini-profiler', require: false
gem 'rack-mini-profiler', '>= 3.1.1', require: false
gem 'stackprof'
end
+74 -66
View File
@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
actioncable (7.0.7.2)
actionpack (= 7.0.7.2)
activesupport (= 7.0.7.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionmailbox (7.0.7.2)
actionpack (= 7.0.7.2)
activejob (= 7.0.7.2)
activerecord (= 7.0.7.2)
activestorage (= 7.0.7.2)
activesupport (= 7.0.7.2)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5.1)
actionpack (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionmailer (7.0.7.2)
actionpack (= 7.0.7.2)
actionview (= 7.0.7.2)
activejob (= 7.0.7.2)
activesupport (= 7.0.7.2)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionpack (7.0.7.2)
actionview (= 7.0.7.2)
activesupport (= 7.0.7.2)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.5.1)
actionpack (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
actiontext (7.0.7.2)
actionpack (= 7.0.7.2)
activerecord (= 7.0.7.2)
activestorage (= 7.0.7.2)
activesupport (= 7.0.7.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
actionview (7.0.7.2)
activesupport (= 7.0.7.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
activejob (7.0.7.2)
activesupport (= 7.0.7.2)
globalid (>= 0.3.6)
activemodel (7.0.5.1)
activesupport (= 7.0.5.1)
activerecord (7.0.5.1)
activemodel (= 7.0.5.1)
activesupport (= 7.0.5.1)
activemodel (7.0.7.2)
activesupport (= 7.0.7.2)
activerecord (7.0.7.2)
activemodel (= 7.0.7.2)
activesupport (= 7.0.7.2)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activesupport (= 7.0.5.1)
activestorage (7.0.7.2)
actionpack (= 7.0.7.2)
activejob (= 7.0.7.2)
activerecord (= 7.0.7.2)
activesupport (= 7.0.7.2)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5.1)
activesupport (7.0.7.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -162,7 +162,7 @@ GEM
byebug (11.1.3)
climate_control (1.2.0)
coderay (1.1.3)
commonmarker (0.23.9)
commonmarker (0.23.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
crack (0.4.5)
@@ -429,7 +429,7 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
lograge (0.12.0)
lograge (0.13.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@@ -452,13 +452,15 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_portile2 (2.8.4)
minitest (5.18.1)
minitest (5.19.0)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.3.0)
neighbor (0.2.3)
activerecord (>= 5.2)
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.3.6)
@@ -532,6 +534,7 @@ GEM
pg_search (2.3.6)
activerecord (>= 5.2)
activesupport (>= 5.2)
pgvector (0.1.1)
procore-sift (1.0.0)
activerecord (>= 6.1)
pry (0.14.2)
@@ -540,18 +543,18 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
puma (6.2.2)
puma (6.3.1)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.1)
rack (2.2.7)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-mini-profiler (3.1.0)
rack-mini-profiler (3.1.1)
rack (>= 1.2.0)
rack-protection (3.0.6)
rack
@@ -560,20 +563,20 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.5.1)
actioncable (= 7.0.5.1)
actionmailbox (= 7.0.5.1)
actionmailer (= 7.0.5.1)
actionpack (= 7.0.5.1)
actiontext (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activemodel (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
rails (7.0.7.2)
actioncable (= 7.0.7.2)
actionmailbox (= 7.0.7.2)
actionmailer (= 7.0.7.2)
actionpack (= 7.0.7.2)
actiontext (= 7.0.7.2)
actionview (= 7.0.7.2)
activejob (= 7.0.7.2)
activemodel (= 7.0.7.2)
activerecord (= 7.0.7.2)
activestorage (= 7.0.7.2)
activesupport (= 7.0.7.2)
bundler (>= 1.15.0)
railties (= 7.0.5.1)
railties (= 7.0.7.2)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
@@ -581,9 +584,9 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
railties (7.0.7.2)
actionpack (= 7.0.7.2)
activesupport (= 7.0.7.2)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -617,6 +620,8 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
@@ -807,7 +812,7 @@ GEM
working_hours (1.4.1)
activesupport (>= 3.2)
tzinfo
zeitwerk (2.6.8)
zeitwerk (2.6.9)
PLATFORMS
arm64-darwin-20
@@ -881,9 +886,10 @@ DEPENDENCIES
line-bot-api
liquid
listen
lograge (~> 0.12.0)
lograge (~> 0.13.0)
maxminddb
mock_redis
neighbor
newrelic-sidekiq-metrics
newrelic_rpm
omniauth
@@ -892,19 +898,21 @@ DEPENDENCIES
omniauth-rails_csrf_protection (~> 1.0)
pg
pg_search
pgvector
procore-sift
pry-rails
puma
pundit
rack-attack
rack-attack (>= 6.7.0)
rack-cors
rack-mini-profiler
rack-mini-profiler (>= 3.1.1)
rack-timeout
rails (~> 7.0.5.1)
rails (~> 7.0.7.2)
redis
redis-namespace
responders
rest-client
reverse_markdown
rspec-rails
rspec_junit_formatter
rubocop
@@ -14,8 +14,18 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
@facebook_inbox = Current.account.inboxes.create!(name: inbox_name, channel: facebook_channel)
set_instagram_id(page_access_token, facebook_channel)
set_avatar(@facebook_inbox, page_id)
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
end
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
Rails.logger.error "Error in register_facebook_page: #{e.message}"
# Additional log statements
log_additional_info
end
def log_additional_info
Rails.logger.debug do
"user_access_token: #{params[:user_access_token]} , page_access_token: #{params[:page_access_token]} ,
page_id: #{params[:page_id]}, inbox_name: #{params[:inbox_name]}"
end
end
@@ -30,6 +40,8 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
instagram_id = response['instagram_business_account']['id']
facebook_channel.update(instagram_id: instagram_id)
rescue StandardError => e
Rails.logger.error "Error in set_instagram_id: #{e.message}"
end
# get params[:inbox_id], current_account. params[:omniauth_token]
@@ -61,6 +73,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
fb_page&.reauthorized!
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
Rails.logger.error "Error in update_fb_page: #{e.message}"
end
end
@@ -77,7 +90,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
koala = Koala::Facebook::OAuth.new(GlobalConfigService.load('FB_APP_ID', ''), GlobalConfigService.load('FB_APP_SECRET', ''))
koala.exchange_access_token_info(omniauth_token)['access_token']
rescue StandardError => e
Rails.logger.error e
Rails.logger.error "Error in long_lived_token: #{e.message}"
end
def mark_already_existing_facebook_pages(data)
@@ -4,6 +4,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
sort_on :name, internal_name: :order_on_name, type: :scope, scope_params: [:direction]
sort_on :phone_number, type: :string
sort_on :last_activity_at, internal_name: :order_on_last_activity_at, type: :scope, scope_params: [:direction]
sort_on :created_at, internal_name: :order_on_created_at, type: :scope, scope_params: [:direction]
sort_on :company, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction]
sort_on :city, internal_name: :order_on_city, type: :scope, scope_params: [:direction]
sort_on :country, internal_name: :order_on_country_name, type: :scope, scope_params: [:direction]
@@ -1,27 +1,27 @@
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
before_action :check_admin_authorization?
before_action :fetch_hook, only: [:update, :destroy]
before_action :fetch_hook, only: [:update, :destroy, :list_all_channels]
def list_all_channels
@channels = channel_builder.fetch_channels
end
def create
ActiveRecord::Base.transaction do
builder = Integrations::Slack::HookBuilder.new(
account: Current.account,
code: params[:code],
inbox_id: params[:inbox_id]
)
@hook = builder.perform
create_chatwoot_slack_channel
end
hook_builder = Integrations::Slack::HookBuilder.new(
account: Current.account,
code: params[:code],
inbox_id: params[:inbox_id]
)
@hook = hook_builder.perform
end
def update
create_chatwoot_slack_channel
render json: @hook
@hook = channel_builder.update(permitted_params[:reference_id])
render json: { error: I18n.t('errors.slack.invalid_channel_id') }, status: :unprocessable_entity if @hook.blank?
end
def destroy
@hook.destroy!
head :ok
end
@@ -31,11 +31,11 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'slack')
end
def create_chatwoot_slack_channel
channel = params[:channel] || 'customer-conversations'
builder = Integrations::Slack::ChannelBuilder.new(
hook: @hook, channel: channel
)
builder.perform
def channel_builder
Integrations::Slack::ChannelBuilder.new(hook: @hook)
end
def permitted_params
params.permit(:reference_id)
end
end
@@ -1,4 +1,7 @@
class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController
include Events::Types
before_action :set_conversation, only: [:toggle_typing, :update_last_seen]
def index
@conversations = @contact_inbox.hmac_verified? ? @contact.conversations : @contact_inbox.conversations
end
@@ -7,12 +10,36 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
@conversation = create_conversation
end
def toggle_typing
case params[:typing_status]
when 'on'
trigger_typing_event(CONVERSATION_TYPING_ON)
when 'off'
trigger_typing_event(CONVERSATION_TYPING_OFF)
end
head :ok
end
def update_last_seen
@conversation.contact_last_seen_at = DateTime.now.utc
@conversation.save!
head :ok
end
private
def set_conversation
@conversation = @contact_inbox.contact.conversations.find_by!(display_id: params[:id])
end
def create_conversation
::Conversation.create!(conversation_params)
end
def trigger_typing_event(event)
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: @conversation.contact)
end
def conversation_params
{
account_id: @contact_inbox.contact.account_id,
@@ -8,13 +8,22 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
def index
@articles = @portal.articles
@articles = @articles.search(list_params) if list_params.present?
@articles.order(position: :asc)
order_by_sort_param
@articles.page(list_params[:page]) if list_params[:page].present?
end
def show; end
private
def order_by_sort_param
@articles = if list_params[:sort].present? && list_params[:sort] == 'views'
@articles.order_by_views
else
@articles.order_by_position
end
end
def set_article
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
@article.increment_view_count
@@ -35,7 +44,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
end
def list_params
params.permit(:query, :locale)
params.permit(:query, :locale, :sort)
end
def permitted_params
@@ -1,8 +1,13 @@
class Public::Api::V1::Portals::BaseController < PublicController
before_action :show_plain_layout
around_action :set_locale
private
def show_plain_layout
@is_plain_layout_enabled = params[:show_plain_layout] == 'true'
end
def set_locale(&)
switch_locale_with_portal(&) if params[:locale].present?
switch_locale_with_article(&) if params[:article_slug].present?
+4 -1
View File
@@ -86,7 +86,10 @@ class AccountDashboard < Administrate::BaseDashboard
"##{account.id} #{account.name}"
end
def permitted_attributes
# We do not use the action parameter but we still need to define it
# to prevent an error from being raised (wrong number of arguments)
# Reference: https://github.com/thoughtbot/administrate/pull/2356/files#diff-4e220b661b88f9a19ac527c50d6f1577ef6ab7b0bed2bfdf048e22e6bfa74a05R204
def permitted_attributes(action)
super + [limits: {}]
end
end
+5 -23
View File
@@ -2,8 +2,8 @@
<div
v-if="!authUIFlags.isFetching"
id="app"
class="app-wrapper app-root"
:class="{ 'app-rtl--wrapper': isRTLView, dark: theme === 'dark' }"
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" />
@@ -35,12 +35,11 @@ import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer';
import rtlMixin from 'shared/mixins/rtlMixin';
import { LocalStorage } from 'shared/helpers/localStorage';
import { setColorTheme } from './helper/themeHelper';
import {
registerSubscription,
verifyServiceWorkerExistence,
} from './helper/pushHelper';
import { LOCAL_STORAGE_KEYS } from './constants/localStorage';
export default {
name: 'App',
@@ -61,7 +60,6 @@ export default {
return {
showAddAccountModal: false,
latestChatwootVersion: null,
theme: 'light',
};
},
@@ -99,27 +97,11 @@ export default {
},
methods: {
initializeColorTheme() {
this.setColorTheme(
window.matchMedia('(prefers-color-scheme: dark)').matches
);
},
setColorTheme(isOSOnDarkMode) {
const selectedColorScheme =
LocalStorage.get(LOCAL_STORAGE_KEYS.COLOR_SCHEME) || 'light';
if (
(selectedColorScheme === 'auto' && isOSOnDarkMode) ||
selectedColorScheme === 'dark'
) {
this.theme = 'dark';
document.body.classList.add('dark');
} else {
this.theme = 'light ';
document.body.classList.remove('dark');
}
setColorTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
},
listenToThemeChanges() {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
mql.onchange = e => this.setColorTheme(e.matches);
mql.onchange = e => setColorTheme(e.matches);
},
setLocale(locale) {
this.$root.$i18n.locale = locale;
+10 -2
View File
@@ -8,11 +8,19 @@ class IntegrationsAPI extends ApiClient {
}
connectSlack(code) {
return axios.post(`${this.baseUrl()}/integrations/slack`, {
code: code,
return axios.post(`${this.baseUrl()}/integrations/slack`, { code });
}
updateSlack({ referenceId }) {
return axios.patch(`${this.baseUrl()}/integrations/slack`, {
reference_id: referenceId,
});
}
listAllSlackChannels() {
return axios.get(`${this.baseUrl()}/integrations/slack/list_all_channels`);
}
delete(integrationId) {
return axios.delete(`${this.baseUrl()}/integrations/${integrationId}`);
}
@@ -11,7 +11,9 @@ describe('#integrationAPI', () => {
expect(integrationAPI).toHaveProperty('update');
expect(integrationAPI).toHaveProperty('delete');
expect(integrationAPI).toHaveProperty('connectSlack');
expect(integrationAPI).toHaveProperty('createHook');
expect(integrationAPI).toHaveProperty('updateSlack');
expect(integrationAPI).toHaveProperty('updateSlack');
expect(integrationAPI).toHaveProperty('listAllSlackChannels');
expect(integrationAPI).toHaveProperty('deleteHook');
});
describeWithAPIMock('API calls', context => {
@@ -26,6 +28,24 @@ describe('#integrationAPI', () => {
);
});
it('#updateSlack', () => {
const updateObj = { referenceId: 'SDFSDGSVE' };
integrationAPI.updateSlack(updateObj);
expect(context.axiosMock.patch).toHaveBeenCalledWith(
'/api/v1/integrations/slack',
{
reference_id: updateObj.referenceId,
}
);
});
it('#listAllSlackChannels', () => {
integrationAPI.listAllSlackChannels();
expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v1/integrations/slack/list_all_channels'
);
});
it('#delete', () => {
integrationAPI.delete(2);
expect(context.axiosMock.delete).toHaveBeenCalledWith(
@@ -0,0 +1,34 @@
<svg width="495" height="384" viewBox="0 0 495 384" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="No Chat History">
<path id="Fill 1" fill-rule="evenodd" clip-rule="evenodd" d="M266.903 0C221.687 0 180.128 15.7196 147.313 42.0198H94.2771C87.3866 42.0198 81.749 47.6925 81.749 54.6257C81.749 61.559 87.3866 67.2317 94.2771 67.2317H121.268H129.178C136.068 67.2317 141.706 72.9043 141.706 79.8376C141.706 83.3 140.294 86.4599 138.023 88.7374C135.759 91.0233 132.619 92.4435 129.178 92.4435H112.303H102.986H57.5281C50.6376 92.4435 45 98.1162 45 105.049C45 111.983 50.6376 117.655 57.5281 117.655H90.071C80.2473 140.886 74.8059 166.446 74.8059 193.291C74.8059 203.031 75.5242 212.603 76.9023 221.957C77.4786 225.865 78.1718 229.739 78.982 233.571C102.618 229.932 127.691 227.008 153.866 224.873L155.837 226.68L157.692 224.571C189.137 222.108 222.144 220.797 256.187 220.797C328.516 220.797 396.201 226.714 454.072 237C456.302 227.268 457.806 217.259 458.516 207.023C458.841 202.485 459 197.905 459 193.291C459 86.5356 372.999 0 266.903 0Z" fill="#43484C"/>
<path id="Fill 3" fill-rule="evenodd" clip-rule="evenodd" d="M20.2465 91.4282H12.6678C5.7005 91.4282 0 97.1882 0 104.228C0 111.268 5.7005 117.028 12.6678 117.028H20.2465C27.2137 117.028 32.9142 111.268 32.9142 104.228C32.9142 97.1882 27.2137 91.4282 20.2465 91.4282Z" fill="#43484C"/>
<g id="Group 7">
<mask id="mask0_201_141" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="78" y="219" width="417" height="165">
<path id="Clip 6" fill-rule="evenodd" clip-rule="evenodd" d="M495 219V384H286.5H78V219H495Z" fill="white"/>
</mask>
<g mask="url(#mask0_201_141)">
<path id="Fill 5" fill-rule="evenodd" clip-rule="evenodd" d="M495 279.775C495 286.675 489.336 292.321 482.413 292.321H402.016C398.559 292.321 395.405 293.734 393.131 296.009C390.849 298.276 389.43 301.421 389.43 304.867C389.43 311.768 395.094 317.413 402.016 317.413H416.954C420.41 317.413 423.566 318.827 425.84 321.102C428.122 323.369 429.54 326.514 429.54 329.96C429.54 336.86 423.876 342.506 416.954 342.506H382.608C350.344 366.662 310.251 380.981 266.802 380.981C174.004 380.981 96.5026 315.699 78 228.695C101.747 225.073 126.937 222.162 153.235 220.038L155.215 221.836L157.079 219.736C188.671 217.286 221.833 215.981 256.036 215.981C328.703 215.981 396.705 221.869 454.847 232.107C452.02 244.302 448.026 256.045 442.991 267.228H482.413C485.879 267.228 489.017 268.642 491.3 270.917C493.582 273.184 495 276.328 495 279.775Z" fill="#34393D"/>
</g>
</g>
<path id="Fill 8" fill-rule="evenodd" clip-rule="evenodd" d="M459 206.12C458.285 216.665 456.771 226.975 454.525 237C396.235 226.404 328.06 220.31 255.208 220.31C220.919 220.31 187.673 221.66 156 224.196L177.965 198.615L212.134 221.028L258.388 208.19L337.959 220.31L372.503 208.19L381.252 220.31L402.126 204.736L421.632 222.707L452.396 198L459 206.12Z" fill="#55575E"/>
<path id="Fill 10" fill-rule="evenodd" clip-rule="evenodd" d="M344.905 216.502C353.565 203.915 358.526 188.987 358.397 173.012C358.043 128.578 318.46 92.8734 269.986 93.2602C221.512 93.6478 182.505 129.983 182.86 174.416C183.214 218.849 222.798 254.555 271.271 254.168C288.59 254.03 304.7 249.3 318.238 241.253L354.454 249.78L344.905 216.502Z" fill="#686B73"/>
<path id="Fill 12" fill-rule="evenodd" clip-rule="evenodd" d="M245.029 171.813C245.069 176.862 241.008 180.988 235.959 181.028C230.909 181.069 226.783 177.008 226.743 171.959C226.703 166.909 230.763 162.784 235.813 162.743C240.861 162.703 244.987 166.763 245.029 171.813Z" fill="#BEC0D6"/>
<path id="Fill 14" fill-rule="evenodd" clip-rule="evenodd" d="M277.942 171.812C277.982 176.862 273.922 180.988 268.872 181.028C263.822 181.068 259.696 177.008 259.657 171.959C259.616 166.909 263.677 162.783 268.726 162.743C273.775 162.703 277.901 166.763 277.942 171.812Z" fill="#BEC0D6"/>
<path id="Fill 16" fill-rule="evenodd" clip-rule="evenodd" d="M310.856 171.812C310.897 176.861 306.836 180.987 301.786 181.028C296.737 181.068 292.611 177.008 292.571 171.958C292.53 166.908 296.591 162.783 301.641 162.743C306.689 162.702 310.815 166.763 310.856 171.812Z" fill="#BEC0D6"/>
<path id="Fill 20" fill-rule="evenodd" clip-rule="evenodd" d="M173.714 235.887C173.714 241.693 175.571 247.099 178.763 251.649L175.376 263.789L188.564 260.581C193.523 263.465 199.402 265.143 205.714 265.143C223.388 265.143 237.714 252.044 237.714 235.887C237.714 219.728 223.388 206.629 205.714 206.629C188.041 206.629 173.714 219.728 173.714 235.887Z" fill="url(#paint0_linear_201_141)"/>
<g id="Frame 148">
<path id="Fill 26" fill-rule="evenodd" clip-rule="evenodd" d="M189 234.658C189 236.677 190.637 238.314 192.657 238.314C194.676 238.314 196.314 236.677 196.314 234.658C196.314 232.638 194.676 231 192.657 231C190.637 231 189 232.638 189 234.658Z" fill="#0D0D0D"/>
<path id="Fill 24" fill-rule="evenodd" clip-rule="evenodd" d="M202.314 234.658C202.314 236.677 203.951 238.314 205.971 238.314C207.99 238.314 209.628 236.677 209.628 234.658C209.628 232.638 207.99 231 205.971 231C203.951 231 202.314 232.638 202.314 234.658Z" fill="#0D0D0D"/>
<path id="Fill 22" fill-rule="evenodd" clip-rule="evenodd" d="M215.629 234.658C215.629 236.677 217.265 238.314 219.285 238.314C221.305 238.314 222.943 236.677 222.943 234.658C222.943 232.638 221.305 231 219.285 231C217.265 231 215.629 232.638 215.629 234.658Z" fill="#0D0D0D"/>
</g>
<path id="Fill 28" fill-rule="evenodd" clip-rule="evenodd" d="M153.6 223.557C127.481 225.685 102.461 228.601 78.8755 232.228C78.0671 228.408 77.3754 224.546 76.8003 220.65L117.229 190.171L153.6 223.557Z" fill="#55575E"/>
<path id="Fill 30" fill-rule="evenodd" clip-rule="evenodd" d="M336.457 286.171C336.457 289.706 304.529 292.571 265.143 292.571C225.757 292.571 193.829 289.706 193.829 286.171C193.829 282.638 225.757 279.771 265.143 279.771C304.529 279.771 336.457 282.638 336.457 286.171Z" fill="#686B73"/>
<path id="Fill 32" fill-rule="evenodd" clip-rule="evenodd" d="M453.486 345.599C446.446 345.599 440.686 339.839 440.686 332.799C440.686 325.76 446.446 320 453.486 320C460.526 320 466.286 325.76 466.286 332.799C466.286 339.839 460.526 345.599 453.486 345.599Z" fill="#34393D"/>
</g>
<defs>
<linearGradient id="paint0_linear_201_141" x1="205.714" y1="177.372" x2="147.434" y2="241.115" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DC9FB"/>
<stop offset="1" stop-color="#9DC9FB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -0,0 +1,33 @@
<svg width="495" height="387" viewBox="0 0 495 387" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="No Chat History">
<path id="Fill 1" fill-rule="evenodd" clip-rule="evenodd" d="M267.927 1.5C222.792 1.5 181.307 17.1457 148.551 43.3222H95.611C88.7329 43.3222 83.1054 48.9682 83.1054 55.8688C83.1054 62.7695 88.7329 68.4155 95.611 68.4155H122.554H130.449C137.327 68.4155 142.955 74.0615 142.955 80.9621C142.955 84.4083 141.546 87.5533 139.278 89.8201C137.019 92.0952 133.884 93.5088 130.449 93.5088H113.605H104.304H58.9279C52.0499 93.5088 46.4224 99.1548 46.4224 106.055C46.4224 112.956 52.0499 118.602 58.9279 118.602H91.4124C81.6064 141.723 76.1748 167.164 76.1748 193.882C76.1748 203.576 76.8918 213.103 78.2674 222.413C78.8426 226.303 79.5346 230.159 80.3433 233.973C103.937 230.351 128.965 227.44 155.093 225.316L157.061 227.114L158.912 225.014C190.301 222.564 223.249 221.259 257.23 221.259C329.429 221.259 396.993 227.147 454.76 237.385C456.986 227.699 458.487 217.737 459.196 207.549C459.521 203.033 459.679 198.474 459.679 193.882C459.679 87.6286 373.832 1.5 267.927 1.5Z" fill="#F1F2F7"/>
<path id="Fill 3" fill-rule="evenodd" clip-rule="evenodd" d="M20.954 92.9282H13.3753C6.40802 92.9282 0.70752 98.6882 0.70752 105.728C0.70752 112.768 6.40802 118.528 13.3753 118.528H20.954C27.9213 118.528 33.6218 112.768 33.6218 105.728C33.6218 98.6882 27.9213 92.9282 20.954 92.9282Z" fill="#F1F2F7"/>
<g id="Group 7">
<mask id="mask0_156_62" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="80" y="221" width="415" height="165">
<path id="Clip 6" fill-rule="evenodd" clip-rule="evenodd" d="M494.293 221.536V385.5H287.188H80.083V221.536H494.293Z" fill="white"/>
</mask>
<g mask="url(#mask0_156_62)">
<path id="Fill 5" fill-rule="evenodd" clip-rule="evenodd" d="M494.293 284.929C494.293 291.786 488.667 297.396 481.79 297.396H401.932C398.498 297.396 395.364 298.801 393.106 301.062C390.838 303.314 389.429 306.439 389.429 309.864C389.429 316.721 395.055 322.331 401.932 322.331H416.769C420.202 322.331 423.337 323.736 425.596 325.997C427.863 328.249 429.271 331.374 429.271 334.799C429.271 341.656 423.645 347.266 416.769 347.266H382.653C350.604 371.271 310.78 385.5 267.622 385.5C175.444 385.5 98.4618 320.627 80.083 234.169C103.671 230.57 128.693 227.678 154.815 225.567L156.781 227.354L158.632 225.268C190.013 222.832 222.954 221.536 256.928 221.536C329.108 221.536 396.656 227.387 454.409 237.561C451.601 249.679 447.633 261.349 442.632 272.461H481.79C485.233 272.461 488.35 273.866 490.617 276.127C492.884 278.379 494.293 281.504 494.293 284.929Z" fill="#E8EBF2"/>
</g>
</g>
<path id="Fill 8" fill-rule="evenodd" clip-rule="evenodd" d="M457.85 206.981C457.142 217.363 455.643 227.515 453.42 237.386C395.73 226.953 328.256 220.953 256.153 220.953C222.216 220.953 189.312 222.282 157.964 224.779L179.704 199.591L213.521 221.659L259.3 209.019L338.053 220.953L372.242 209.019L380.901 220.953L401.56 205.618L420.866 223.313L451.314 198.986L457.85 206.981Z" fill="#E0E2EE"/>
<path id="Fill 10" fill-rule="evenodd" clip-rule="evenodd" d="M345.614 218.002C354.273 205.415 359.234 190.487 359.106 174.512C358.752 130.078 319.168 94.3734 270.694 94.7602C222.22 95.1478 183.213 131.483 183.568 175.916C183.923 220.349 223.506 256.055 271.979 255.668C289.298 255.53 305.408 250.8 318.946 242.753L355.162 251.28L345.614 218.002Z" fill="#D8DBEA"/>
<path id="Fill 12" fill-rule="evenodd" clip-rule="evenodd" d="M245.736 173.313C245.776 178.362 241.716 182.488 236.667 182.528C231.616 182.569 227.49 178.508 227.451 173.459C227.411 168.409 231.471 164.284 236.52 164.243C241.569 164.203 245.695 168.263 245.736 173.313Z" fill="#BEC0D6"/>
<path id="Fill 14" fill-rule="evenodd" clip-rule="evenodd" d="M278.65 173.312C278.69 178.362 274.629 182.488 269.58 182.528C264.53 182.568 260.404 178.508 260.365 173.459C260.323 168.409 264.384 164.283 269.434 164.243C274.483 164.203 278.609 168.263 278.65 173.312Z" fill="#BEC0D6"/>
<path id="Fill 16" fill-rule="evenodd" clip-rule="evenodd" d="M311.565 173.312C311.605 178.361 307.544 182.487 302.495 182.528C297.445 182.568 293.319 178.508 293.28 173.458C293.239 168.408 297.299 164.283 302.349 164.243C307.398 164.202 311.524 168.263 311.565 173.312Z" fill="#BEC0D6"/>
<path id="Fill 18" fill-rule="evenodd" clip-rule="evenodd" d="M157.965 224.585L156.192 226.414L154.308 224.848C155.521 224.753 156.744 224.666 157.965 224.585Z" fill="#F1F2F7"/>
<path id="Fill 20" fill-rule="evenodd" clip-rule="evenodd" d="M174.422 237.387C174.422 243.193 176.279 248.599 179.47 253.149L176.083 265.289L189.272 262.081C194.23 264.965 200.11 266.643 206.422 266.643C224.095 266.643 238.422 253.544 238.422 237.387C238.422 221.228 224.095 208.129 206.422 208.129C188.748 208.129 174.422 221.228 174.422 237.387Z" fill="url(#paint0_linear_156_62)"/>
<path id="Fill 22" fill-rule="evenodd" clip-rule="evenodd" d="M214.651 237.386C214.651 239.405 216.287 241.043 218.308 241.043C220.327 241.043 221.965 239.405 221.965 237.386C221.965 235.366 220.327 233.728 218.308 233.728C216.287 233.728 214.651 235.366 214.651 237.386Z" fill="white"/>
<path id="Fill 24" fill-rule="evenodd" clip-rule="evenodd" d="M203.679 237.386C203.679 239.405 205.316 241.043 207.336 241.043C209.355 241.043 210.993 239.405 210.993 237.386C210.993 235.366 209.355 233.728 207.336 233.728C205.316 233.728 203.679 235.366 203.679 237.386Z" fill="white"/>
<path id="Fill 26" fill-rule="evenodd" clip-rule="evenodd" d="M190.879 237.386C190.879 239.405 192.516 241.043 194.537 241.043C196.556 241.043 198.194 239.405 198.194 237.386C198.194 235.366 196.556 233.728 194.537 233.728C192.516 233.728 190.879 235.366 190.879 237.386Z" fill="white"/>
<path id="Fill 28" fill-rule="evenodd" clip-rule="evenodd" d="M154.308 225.057C128.188 227.185 103.169 230.101 79.583 233.728C78.7746 229.908 78.0829 226.046 77.5078 222.15L117.936 191.671L154.308 225.057Z" fill="#E0E2EE"/>
<path id="Fill 30" fill-rule="evenodd" clip-rule="evenodd" d="M337.164 287.671C337.164 291.206 305.235 294.071 265.85 294.071C226.464 294.071 194.536 291.206 194.536 287.671C194.536 284.138 226.464 281.271 265.85 281.271C305.235 281.271 337.164 284.138 337.164 287.671Z" fill="#D8DBEA"/>
<path id="Fill 32" fill-rule="evenodd" clip-rule="evenodd" d="M454.193 347.099C447.153 347.099 441.393 341.339 441.393 334.299C441.393 327.26 447.153 321.5 454.193 321.5C461.233 321.5 466.993 327.26 466.993 334.299C466.993 341.339 461.233 347.099 454.193 347.099Z" fill="#E1E3EF"/>
</g>
<defs>
<linearGradient id="paint0_linear_156_62" x1="206.422" y1="178.872" x2="148.142" y2="242.615" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DC9FB"/>
<stop offset="1" stop-color="#9DC9FB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -1,66 +1,64 @@
@import '~vue2-datepicker/scss/index';
.mx-datepicker-popup {
z-index: 99999;
@apply z-[99999];
}
.date-picker {
&.no-margin {
.mx-input {
margin-bottom: 0;
@apply mb-0;
}
}
&:not(.auto-width) {
.mx-datepicker-range {
width: 320px;
@apply w-[320px];
}
}
.mx-datepicker {
width: 100%;
@apply w-full;
}
.mx-input {
border: 1px solid var(--s-200);
border-radius: var(--border-radius-normal);
box-shadow: none;
display: flex;
height: 2.5rem;
@apply h-[2.5rem] flex border border-solid border-slate-200 dark:border-slate-600 rounded-md shadow-none;
}
.mx-input:disabled,
.mx-input[readonly] {
background-color: var(--white);
cursor: pointer;
@apply bg-white dark:bg-slate-900 cursor-pointer;
}
}
.mx-calendar-content .cell:hover {
@apply bg-slate-75 dark:bg-slate-700 text-slate-900 dark:text-slate-100;
}
.mx-datepicker-inline {
width: 100%;
@apply w-full;
.mx-calendar {
width: 100%;
@apply w-full;
}
.cell.disabled {
background-color: var(--s-25);
color: var(--s-200);
@apply bg-slate-25 dark:bg-slate-900 text-slate-200 dark:text-slate-300;
}
.mx-time-item.disabled {
background-color: var(--s-25);
@apply bg-slate-25 dark:bg-slate-900;
}
.today {
font-weight: var(--font-weight-bold);
@apply font-semibold;
}
.mx-datepicker-main {
border: 0;
@apply border-0 bg-white dark:bg-slate-800;
}
.mx-time-header {
border: 0;
@apply border-0;
}
}
@@ -16,10 +16,6 @@ select {
padding: var(--space-normal);
}
.button-wrapper .button.grey-btn {
margin-left: var(--space-normal);
}
code {
border: 0;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
@@ -30,6 +26,7 @@ code {
background: $color-background;
border-radius: var(--border-radius-large);
padding: $space-two;
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100;
}
}
@@ -9,87 +9,25 @@ body {
}
.app-wrapper {
@include full-height;
flex-grow: 0;
min-height: 0;
width: 100%;
@apply h-full flex-grow-0 min-h-0 w-full;
.button--fixed-top {
@apply fixed ltr:right-2 rtl:left-2 top-2 flex flex-row;
}
}
.banner + .app-wrapper {
// Reduce the height of the dashboard to make room for the banner.
// And causing the top right green-action button to be pushed down when scrolling.
@apply h-[calc(100%-48px)];
.button--fixed-top {
top: 5.6 * $space-one;
@apply top-14;
}
.off-canvas-content {
.button--fixed-top {
top: $space-small;
@apply top-2;
}
}
}
is-closed .app-root {
@include flex;
flex-direction: column;
}
.app-content {
@include flex;
@include full-height;
min-height: 0;
overflow: hidden;
}
.view-box {
@include full-height;
@include space-between-column;
height: 100vh;
margin: 0;
}
.view-panel {
flex-direction: column;
margin: 0;
overflow-y: auto;
padding: $space-normal;
}
.content-box {
overflow: auto;
padding: $space-normal;
}
.back-button {
@include flex;
align-items: center;
color: $color-woot;
cursor: pointer;
font-size: $font-size-default;
font-weight: $font-weight-normal;
margin-right: $space-normal;
&::before {
font-size: $font-size-large;
margin-right: $space-small;
vertical-align: text-bottom;
}
}
.button-spinner {
float: right;
}
.no-items-error-message {
@include flex;
@include full-height;
align-items: center;
flex-direction: column;
justify-content: center;
img {
max-width: $space-mega;
padding: $space-one;
}
}
@@ -1,44 +1,6 @@
.app-rtl--wrapper {
direction: rtl;
// Primary sidebar
.primary--sidebar {
.options-menu.dropdown-pane {
right: var(--space-smaller);
.auto-offline--toggle {
padding: var(--space-smaller) var(--space-one) var(--space-smaller)
var(--space-smaller);
}
.status-items .button {
text-align: right;
}
}
}
// Settings header
.settings-header {
.header--icon {
margin-left: var(--space-small);
margin-right: var(--space-smaller);
}
}
.header-section.back-button {
direction: initial;
margin-left: var(--space-normal);
margin-right: var(--space-smaller);
}
// Settings header action button
.button--fixed-top {
left: $space-small;
position: fixed;
right: unset;
top: $space-small;
}
// Woot Tabs
.tabs-title {
&:first-child {
@@ -73,23 +35,6 @@
// Conversation details
.conversation-details-wrap {
.conv-header {
.user {
margin-left: var(--space-normal);
margin-right: unset;
.user--profile__meta {
margin-left: unset;
margin-right: var(--space-small);
}
}
.actions--container .resolve-actions {
margin-left: unset;
margin-right: var(--space-small);
}
}
.conversation-panel {
// Message text
.text-content {
@@ -137,11 +82,6 @@
}
}
// Conversation sidebar toggle button
.sidebar-toggle--button {
transform: rotate(180deg);
}
// Conversation sidebar close button
.close-button--rtl {
transform: rotate(180deg);
@@ -285,31 +225,6 @@
}
}
// scss-lint:disable SelectorDepth
.container .header-wrap .header-left-wrap .header-left-wrap > .page-title {
margin-right: var(--space-small);
}
.portal-container .container {
margin-left: unset !important;
margin-right: var(--space-small);
.configuration-items--wrap {
margin-left: var(--space-mega);
margin-right: unset !important;
}
thead th {
padding-left: var(--space-small);
padding-right: 0;
}
tbody td {
padding-left: var(--space-small);
padding-right: 0;
}
}
.portal-popover__container .portal {
.actions-container {
margin-left: unset;
@@ -413,11 +328,6 @@
direction: initial;
}
.inbox--name .inbox--icon {
margin-left: var(--space-micro);
margin-right: 0;
}
.colorpicker--chrome {
direction: initial;
}
@@ -71,9 +71,3 @@
align-items: center;
display: flex;
}
.button--fixed-top {
position: fixed;
right: var(--space-small);
top: var(--space-small);
}
@@ -72,7 +72,6 @@
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
@apply bg-slate-900 text-white py-1 px-2 z-40 text-xs rounded-md dark:bg-slate-200 dark:text-slate-900;
}
@@ -7,6 +7,7 @@
z-index: var(--z-index-very-high);
&.dropdown-pane--open {
@apply bg-white dark:bg-slate-800;
display: block;
visibility: visible;
}
@@ -1,93 +1,94 @@
@mixin label-multiselect-hover {
&::after {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
&:hover {
background: $color-background;
@apply bg-slate-50 dark:bg-slate-700;
&::after {
color: $color-woot;
@apply text-woot-500 dark:text-woot-500;
}
}
}
.multiselect {
&:not(.no-margin) {
margin-bottom: var(--space-normal);
@apply mb-4;
}
&.multiselect--disabled {
opacity: 0.8;
@apply opacity-50 border border-slate-200 dark:border-slate-600 rounded-md cursor-not-allowed;
.multiselect__select {
@apply cursor-not-allowed bg-white dark:bg-slate-900 rounded-md;
}
.multiselect__tags {
@apply border-0;
}
}
.multiselect--active {
>.multiselect__tags {
border-color: var(--w-500);
> .multiselect__tags {
@apply border-woot-500 dark:border-woot-500;
}
}
.multiselect__select {
min-height: 2.875rem;
padding: 0;
right: 0;
top: 0;
@apply min-h-[2.875rem] p-0 right-0 top-0;
&::before {
right: 0;
@apply right-0;
}
}
.multiselect__content-wrapper {
@apply bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-600 text-slate-800 dark:text-slate-100;
}
.multiselect__content {
max-width: 100%;
@apply max-w-full;
.multiselect__option {
font-size: $font-size-small;
font-weight: $font-weight-normal;
@apply text-sm font-normal;
span {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: fit-content;
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap w-fit;
}
p {
margin-bottom: 0;
@apply mb-0;
}
&.multiselect__option--highlight {
background: var(--white);
color: var(--color-body);
@apply bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100;
}
&.multiselect__option--highlight:hover {
background: var(--w-50);
color: var(--color-body);
@apply bg-woot-50 dark:bg-woot-600 text-slate-800 dark:text-slate-100;
&::after {
background: var(--w-50);
color: var(--s-600);
@apply bg-woot-50 dark:bg-woot-600 text-slate-600 dark:text-slate-200;
}
}
&.multiselect__option--highlight::after {
background: transparent;
@apply bg-transparent;
}
&.multiselect__option--selected {
background: var(--w-75);
@apply bg-woot-50 dark:bg-woot-600 text-slate-800 dark:text-slate-100;
&.multiselect__option--highlight:hover {
background: var(--w-75);
@apply bg-woot-75 dark:bg-woot-600;
&::after {
background: transparent;
@apply bg-transparent;
}
&::after:hover {
color: var(--color-body);
@apply text-slate-800 dark:text-slate-100;
}
}
}
@@ -95,175 +96,130 @@
}
.multiselect__tags {
border: 1px solid var(--s-200);
border-color: var(--s-200);
margin: 0;
min-height: 2.75rem;
padding-top: $zero;
@apply bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-600 m-0 min-h-[2.875rem] pt-0;
}
.multiselect__tags-wrap {
display: inline-block;
line-height: 1;
margin-top: $space-smaller;
@apply inline-block leading-none mt-1;
}
.multiselect__placeholder {
color: $color-gray;
font-weight: $font-weight-normal;
padding-top: var(--space-slab);
@apply text-slate-400 dark:text-slate-400 font-normal pt-3;
}
.multiselect__tag {
$vertical-space: $space-smaller + $space-micro;
background: $color-background;
color: $color-heading;
margin-top: $space-smaller;
padding: $vertical-space $space-medium $vertical-space $space-one;
@apply bg-slate-50 dark:bg-slate-800 mt-1 text-slate-800 dark:text-slate-100 pr-6 pl-2.5 py-1.5;
}
.multiselect__tag-icon {
@include label-multiselect-hover;
line-height: $space-medium + $space-micro;
}
.multiselect__input {
@include ghost-input;
font-size: $font-size-small;
height: 2.75rem;
margin-bottom: $zero;
padding: 0;
@apply text-sm h-[2.875rem] mb-0 p-0;
}
.multiselect__single {
@include text-ellipsis;
display: inline-block;
margin-bottom: 0;
padding: var(--space-slab) var(--space-one);
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 inline-block mb-0 py-3 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis;
}
}
.sidebar-labels-wrap {
&.has-edited,
&:hover {
.multiselect {
cursor: pointer;
@apply cursor-pointer;
}
}
.multiselect {
>.multiselect__select {
visibility: hidden;
> .multiselect__select {
@apply invisible;
}
>.multiselect__tags {
border-color: transparent;
> .multiselect__tags {
@apply border-transparent;
}
&.multiselect--active>.multiselect__tags {
border-color: $color-woot;
&.multiselect--active > .multiselect__tags {
@apply border-woot-500 dark:border-woot-500;
}
}
}
.multiselect-wrap--small {
$multiselect-height: 2.5rem;
.multiselect__tags,
.multiselect__input {
align-items: center;
display: flex;
@apply items-center flex;
}
.multiselect__tags,
.multiselect__input,
.multiselect {
background: var(--white);
font-size: var(--font-size-small);
height: $multiselect-height;
min-height: $multiselect-height;
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 rounded-[5px] text-sm h-10 min-h-[2.5rem];
}
.multiselect__input {
height: $multiselect-height - $space-micro;
min-height: $multiselect-height - $space-micro;
@apply h-[2.375rem] min-h-[2.375rem];
}
.multiselect__single {
align-items: center;
display: flex;
font-size: var(--font-size-small);
margin: 0;
max-height: 2.375rem;
padding: var(--space-smaller) var(--space-micro);
@apply items-center flex m-0 text-sm max-h-[2.375rem] text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900 py-1 px-0.5;
}
.multiselect__placeholder {
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply m-0 py-1 px-0.5;
}
.multiselect__select {
min-height: $multiselect-height;
@apply min-h-[2.5rem];
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: transparent;
@apply bg-transparent;
}
.multiselect__tags-wrap {
flex-shrink: 0;
@apply flex-shrink-0;
}
}
.multiselect-wrap--medium {
$multiselect-height: 3rem;
.multiselect__tags,
.multiselect__input {
align-items: center;
display: flex;
@apply items-center flex;
}
.multiselect__tags,
.multiselect__input,
.multiselect {
background: var(--white);
font-size: var(--font-size-small);
height: $multiselect-height;
min-height: $multiselect-height;
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 text-sm h-12 min-h-[3rem];
}
.multiselect__input {
height: $multiselect-height - $space-micro;
min-height: $multiselect-height - $space-micro;
@apply h-[2.875rem] min-h-[2.875rem];
}
.multiselect__single {
align-items: center;
display: flex;
font-size: var(--font-size-small);
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply items-center flex m-0 text-sm py-1 px-0.5 text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900;
}
.multiselect__placeholder {
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply m-0 py-1 px-0.5;
}
.multiselect__select {
min-height: $multiselect-height;
@apply min-h-[3rem];
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: transparent;
@apply bg-transparent;
}
.multiselect__tags-wrap {
flex-shrink: 0;
@apply flex-shrink-0;
}
}
@@ -10,7 +10,7 @@
@import 'variables';
@import '~spinkit/scss/spinners/7-three-bounce';
@import '~vue-multiselect/dist/vue-multiselect.min.css';
@import 'vue-multiselect/dist/vue-multiselect.min.css';
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins';
@@ -30,3 +30,18 @@
@import 'widgets/forms';
@import 'plugins/multiselect';
@import 'widget/assets/scss/reset';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'widget/assets/scss/utilities';
html,
body {
font-family: 'PlusJakarta', sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
height: 100%;
}
@@ -1,171 +1,112 @@
.settings {
overflow: auto;
}
// Conversation header - Light BG
.settings-header {
@include flex;
@include flex-align($x: justify, $y: middle);
height: $header-height;
min-height: $header-height;
padding: $space-small $space-normal;
// Resolve Button
.button {
margin: 0;
}
// User thumbnail and text
.page-title {
@include flex;
@include flex-align($x: center, $y: middle);
margin: 0;
}
@apply overflow-auto;
}
.wizard-box {
.item {
// @include background-light;
cursor: pointer;
padding: $space-normal $space-normal $space-normal $space-medium;
position: relative;
@apply cursor-pointer py-4 pr-4 pl-6 relative;
&::before,
&::after {
background: $color-border;
content: '';
height: 100%;
position: absolute;
top: $space-normal;
width: 2px;
@apply bg-slate-75 dark:bg-slate-600 content-[''] h-full absolute top-5 w-0.5;
}
&::before {
height: $space-normal;
top: $zero;
@apply h-4 top-0;
}
&:first-child {
&::before {
height: 0;
@apply h-0;
}
}
&:last-child {
&::after {
height: $zero;
@apply h-0;
}
}
&.active {
h3 {
color: $color-woot;
@apply text-woot-500 dark:text-woot-500;
}
.step {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
}
&.over {
&::after {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
.step {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
& + .item {
&::before {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
}
}
h3 {
color: $color-body;
font-size: $font-size-default;
line-height: 1;
padding-left: $space-medium;
@apply text-slate-800 dark:text-slate-100 text-base pl-6;
}
.completed {
color: $success-color;
margin-left: $space-smaller;
@apply text-green-500 dark:text-green-500 ml-1;
}
p {
color: $color-light-gray;
font-size: $font-size-small;
margin: 0;
padding-left: $space-medium;
@apply text-slate-600 dark:text-slate-300 text-sm m-0 pl-6;
}
.step {
background: $color-border;
border-radius: 20px;
color: $color-white;
font-size: $font-size-micro;
font-weight: $font-weight-medium;
height: $space-normal;
left: $space-normal;
line-height: $space-normal;
position: absolute;
text-align: center;
top: $space-normal;
width: $space-normal;
z-index: 999;
@apply bg-slate-75 dark:bg-slate-600 rounded-2xl font-medium w-4 left-4 leading-4 z-[999] absolute text-center text-white dark:text-white text-xxs top-5;
i {
font-size: $font-size-micro;
@apply text-xxs;
}
}
}
}
.wizard-body {
@include border-light;
@include background-white;
@include full-height();
padding: $space-medium;
@apply border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6;
&.height-auto {
height: auto;
@apply h-auto;
}
}
.settings--content {
margin: $space-small $space-large;
@apply my-2 mx-8;
.title {
font-weight: $font-weight-medium;
@apply font-medium;
}
.code {
background: $color-background;
max-height: $space-mega;
overflow: auto;
padding: $space-one;
white-space: nowrap;
@apply bg-slate-50 dark:bg-slate-800 overflow-auto p-2.5 whitespace-nowrap;
code {
background: transparent;
border: 0;
@apply bg-transparent border-0;
}
}
}
.login-init {
padding-top: 30%;
text-align: center;
@apply pt-[30%] text-center;
p {
padding: $space-medium;
@apply p-6;
}
> a > img {
width: $space-larger * 5;
@apply w-60;
}
}
@@ -1,41 +1 @@
.integrations-wrap {
.integration {
background: $color-white;
border: 1px solid $color-border;
border-radius: $space-smaller;
margin-bottom: $space-normal;
padding: $space-normal;
.integration--image {
display: flex;
height: 6.25rem;
width: 6.25rem;
img {
max-width: 100%;
padding: $space-medium;
}
}
.integration--type {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 var(--space-normal);
}
.integration--title {
font-size: var(--font-size-large);
}
.button-wrap {
@include flex;
@include flex-align(center, middle);
margin-bottom: 0;
}
}
}
.help-wrap {
padding-left: $space-large;
}
// to be removed
@@ -1,5 +1,5 @@
.button {
@apply items-center inline-flex h-10 mb-0;
@apply items-center inline-flex h-10 mb-0 gap-2;
.button__content {
@apply w-full;
@@ -14,17 +14,9 @@
@apply px-2 py-0;
}
.icon--emoji + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
.icon--font + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
// @TODDO - Remove after moving all buttons to woot-button
.icon + .button__content {
@apply pl-2 w-auto rtl:pr-2 rtl:pl-0;
@apply w-auto;
}
&.expanded {
@@ -42,10 +34,10 @@
}
&.hollow {
@apply border border-slate-200 text-woot-700 dark:text-woot-100 hover:bg-woot-50 dark:hover:bg-woot-900;
@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;
&.secondary {
@apply text-slate-700 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
@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 {
@@ -64,19 +56,19 @@
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
&.secondary {
@apply border-slate-100 dark:border-slate-700;
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
}
&.success {
@apply border-slate-100 dark:border-slate-700;
@apply border-slate-100 dark:border-slate-700 text-green-800 dark:text-green-100;
}
&.alert {
@apply border-slate-100 dark:border-slate-700;
@apply border-slate-100 dark:border-slate-700 text-red-700 dark:text-red-100;
}
&.warning {
@apply border-slate-100 dark:border-slate-700;
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-700;
}
}
}
@@ -90,7 +82,7 @@
}
&.success {
@apply bg-green-50 dark:bg-green-700 text-green-700 dark:text-green-100 hover:bg-green-100 dark:hover:bg-green-800;
@apply bg-green-50 dark:bg-green-700 text-green-700 dark:text-green-100 hover:bg-green-100 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
@@ -98,7 +90,7 @@
}
&.warning {
@apply bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-100 hover:bg-yellow-200 dark:hover:bg-yellow-200;
@apply bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700 hover:bg-yellow-200 dark:hover:bg-yellow-200;
}
}
@@ -118,26 +110,26 @@
}
&.warning {
@apply text-yellow-700 dark:text-yellow-100;
@apply text-yellow-700 dark:text-yellow-600;
}
&:hover {
@apply hover:bg-woot-50 dark:hover:bg-woot-300;
@apply hover:bg-woot-50 dark:hover:bg-woot-900/50 hover:text-woot-500 dark:hover:text-woot-100;
&.secondary {
@apply hover:bg-slate-50 dark:hover:bg-slate-700;
@apply hover:bg-slate-50 dark:hover:bg-slate-700 hover:text-slate-800 dark:hover:text-slate-100;
}
&.success {
@apply hover:bg-green-50 dark:hover:bg-green-800;
@apply hover:bg-green-50 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
@apply hover:bg-red-50 dark:hover:bg-red-800;
@apply hover:bg-red-50 dark:hover:bg-red-800 hover:text-red-700 dark:hover:text-red-100;
}
&.warning {
@apply hover:bg-yellow-50 dark:hover:bg-yellow-800;
@apply hover:bg-yellow-100 dark:hover:bg-yellow-800 hover:text-yellow-700 dark:hover:text-yellow-600;
}
}
@@ -157,18 +149,10 @@
// Sizes
&.tiny {
@apply h-6;
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.small {
@apply h-8 pb-1 pt-1;
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.large {
@@ -1,66 +1 @@
$resolve-button-width: 8.25rem;
// Conversation header - Light BG
.conv-header {
@include background-white;
@include flex;
@include flex-align($x: justify, $y: middle);
@include border-normal-bottom;
padding: var(--space-small) var(--space-normal);
.multiselect-box {
@include flex;
@include flex-align($x: justify, $y: middle);
border: 1px solid var(--color-border);
border-radius: var(--space-smaller);
margin-right: var(--space-small);
width: 13.5rem;
.icon {
color: $medium-gray;
font-size: $font-size-default;
line-height: 2.375rem;
padding-left: $space-slab;
padding-right: $space-smaller;
}
.multiselect {
border-radius: var(--border-radius-small);
margin: 0;
min-width: 0;
.multiselect__tags {
border-color: transparent;
}
}
}
// User thumbnail and text
.user {
@include flex;
@include flex-align($x: center, $y: middle);
margin-right: var(--space-normal);
min-width: 0;
.user--profile__meta {
align-items: flex-start;
display: flex;
flex-direction: column;
justify-content: flex-start;
margin-left: var(--space-small);
min-width: 0;
}
}
}
.header-actions-wrap {
align-items: center;
display: flex;
flex-direction: row;
flex-grow: 1;
justify-content: flex-end;
&.has-open-sidebar {
justify-content: flex-end;
}
}
// File to be removed
@@ -10,114 +10,7 @@
}
.conversation {
@include flex;
@include flex-shrink;
border-left: var(--space-micro) solid transparent;
cursor: pointer;
padding: 0 var(--space-normal);
position: relative;
&.active {
animation: left-shift-animation 0.25s $swift-ease-out-function;
background: var(--color-background);
border-left-color: var(--color-woot);
}
.conversation--details {
padding: var(--space-slab) 0;
}
.conversation--user {
font-size: var(--font-size-small);
margin: 0 var(--space-small);
text-transform: capitalize;
.label {
left: var(--space-one);
max-width: var(--space-jumbo);
overflow: hidden;
position: relative;
text-overflow: ellipsis;
top: var(--space-one);
white-space: nowrap;
}
}
.conversation--message {
color: var(--color-body);
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
height: var(--space-medium);
line-height: var(--space-medium);
margin: 0 var(--space-small);
max-width: 96%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 16.875rem;
img,
svg {
display: inline-block;
}
}
.conversation--meta {
@include flex;
flex-direction: column;
position: absolute;
right: var(--space-normal);
top: var(--space-normal);
.unread {
@include round-corner;
@include light-shadow;
background: darken($success-color, 3%);
color: var(--white);
display: none;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
height: var(--space-normal);
line-height: var(--space-normal);
margin-left: auto;
margin-top: var(--space-smaller);
min-width: var(--space-normal);
padding: 0 var(--space-smaller);
text-align: center;
}
.timestamp {
color: $dark-gray;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-normal);
line-height: var(--space-normal);
margin-left: auto;
}
}
&.unread-chat {
.unread {
display: inline-block;
}
.conversation--message {
font-weight: var(--font-weight-bold);
}
.conversation--user {
font-weight: var(--font-weight-bold);
}
}
&.compact {
padding-left: 0;
.conversation--details {
border-radius: var(--border-radius-small);
margin-left: 0;
padding-left: var(--space-two);
padding-right: var(--space-small);
}
}
}
@@ -1,352 +1,229 @@
// scss-lint:disable MergeableSelector
@mixin bubble-with-types {
padding: $space-small $space-normal;
margin: 0;
background: $color-woot;
border-radius: $space-one;
color: var(--white);
font-size: $font-size-small;
font-weight: $font-weight-normal;
position: relative;
.message-text__wrap {
position: relative;
.link {
color: var(--white);
text-decoration: underline;
}
@tailwind utilities;
@layer utilities {
.custom-gradient {
background-image: linear-gradient(
-180deg,
transparent 3%,
rgb(76 81 85) 130%
);
}
.image,
.video {
cursor: pointer;
position: relative;
.bubble-with-types {
@apply py-2 text-sm font-normal bg-woot-500 dark:bg-woot-500 relative px-4 m-0 text-white dark:text-white;
.modal-container {
text-align: center;
.message-text__wrap {
@apply relative;
.link {
@apply text-white dark:text-white underline;
}
}
.modal-image {
max-height: 76vh;
max-width: 76vw;
.image,
.video {
@apply cursor-pointer relative;
.modal-container {
@apply text-center;
}
.modal-image {
@apply max-h-[76vh] max-w-[76vw];
}
.modal-video {
@apply max-h-[76vh] max-w-[76vw];
}
&::before {
@apply custom-gradient bottom-0 h-[20%] content-[''] left-0 absolute w-full opacity-80;
}
}
.modal-video {
max-height: 76vh;
max-width: 76vw;
}
&::before {
background-image: linear-gradient(
-180deg,
transparent 3%,
$color-heading 130%
);
bottom: 0;
content: '';
height: 20%;
left: 0;
opacity: 0.8;
position: absolute;
width: 100%;
}
}
}
.conversations-list-wrap {
@include flex;
border-right: 1px solid var(--s-50);
flex-direction: column;
.load-more-conversations {
font-size: $font-size-small;
margin: 0;
padding: $space-normal;
width: 100%;
}
.end-of-list-text {
padding: $space-normal;
}
.conversations-list {
flex: 1 1;
overflow-y: auto;
@include breakpoint(large up) {
@include scroll-on-hover;
}
}
.chat-list__top {
@include flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--space-normal);
}
.content-box {
text-align: center;
}
}
.conversation-panel {
@include flex;
flex: 1 1 1px;
flex-direction: column;
height: 100%;
margin: 0;
overflow-y: auto;
padding-bottom: var(--space-normal);
position: relative;
@apply flex-shrink flex-grow basis-px flex flex-col overflow-y-auto relative h-full m-0 pb-4;
}
.conversation-panel > li {
@include flex;
@include flex-shrink;
margin: $zero $zero $space-micro;
position: relative;
&:first-child {
margin-top: auto;
}
&:last-child {
margin-bottom: 0;
}
@apply flex flex-shrink-0 flex-grow-0 flex-auto max-w-full mt-0 mr-0 mb-1 ml-0 relative first:mt-auto last:mb-0;
&.unread--toast {
+ .right {
margin-bottom: var(--space-micro);
@apply mb-1;
}
+ .left {
margin-bottom: 0;
@apply mb-0;
}
span {
@include elegant-card;
@include round-corner;
background: $color-woot;
color: var(--white);
font-size: $font-size-mini;
font-weight: $font-weight-medium;
margin: $space-one auto;
padding: $space-smaller $space-two;
@apply shadow-lg rounded-full bg-woot-500 dark:bg-woot-500 text-white dark:text-white text-xs font-medium my-2.5 mx-auto px-2.5 py-1.5;
}
}
.bubble {
@include bubble-with-types;
text-align: left;
word-wrap: break-word;
@apply bubble-with-types text-left break-words;
.aplayer {
box-shadow: none;
@apply shadow-none;
font-family: inherit;
}
}
&.left {
.bubble {
@include border-normal;
background: $white;
border-bottom-left-radius: $space-smaller;
border-top-left-radius: $space-smaller;
color: $color-body;
margin-right: auto;
word-break: break-word;
@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;
&.is-image {
border-radius: var(--border-radius-large);
@apply rounded-lg;
}
.link {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
.file {
.text-block-title {
color: $color-body;
@apply text-slate-700 dark:text-woot-300;
}
.icon-wrap {
color: $color-woot;
@apply text-woot-600 dark:text-woot-600;
}
.download {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
}
}
+ .right {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-right-radius: $space-one;
@apply rounded-tr-lg;
}
}
+ .unread--toast {
+ .right {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-right-radius: $space-one;
@apply rounded-tr-lg;
}
}
+ .left {
margin-top: 0;
@apply mt-0;
}
}
}
&.right {
@include flex-align(right, null);
@apply justify-end;
.wrap {
align-items: flex-end;
display: flex;
margin-right: $space-normal;
text-align: right;
@apply flex items-end mr-4 text-right;
.sender--info {
padding: var(--space-small) 0 var(--space-smaller) var(--space-small);
@apply pt-2 pb-1 pr-0 pl-2;
}
}
.bubble {
border-bottom-right-radius: $space-smaller;
border-top-right-radius: $space-smaller;
margin-left: auto;
word-break: break-word;
@apply ml-auto break-words rounded-l-lg rounded-r;
&.is-private {
background: lighten($warning-color, 32%);
border: 1px solid lighten($warning-color, 15%);
color: $color-heading;
position: relative;
@apply text-black-900 dark:text-white relative border border-solid bg-yellow-100 dark:bg-yellow-700 border-yellow-200 dark:border-yellow-600/25;
&::before {
bottom: 0;
color: $medium-gray;
position: absolute;
right: $space-one;
top: $space-smaller + $space-micro;
blockquote {
@apply border-slate-400 dark:border-slate-400 text-slate-800 dark:text-slate-300;
p {
@apply text-slate-600 dark:text-slate-300;
}
}
}
&.is-image {
border-radius: var(--border-radius-large);
@apply rounded-lg;
}
}
+ .left {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-left-radius: $space-one;
@apply rounded-tl-lg;
}
}
+ .unread--toast {
+ .left {
margin-top: $space-one;
@apply rounded-lg;
.bubble {
border-top-left-radius: $space-one;
@apply rounded-tl-lg;
}
}
+ .right {
margin-top: 0;
@apply mt-0;
}
}
}
&.center {
justify-content: center;
@apply items-center justify-center;
}
.wrap {
--bubble-max-width: 31rem;
margin: $zero $space-normal;
max-width: Min(var(--bubble-max-width), 84%);
max-width: Min(31rem, 84%);
@apply my-0 mx-4;
.sender--name {
font-size: $font-size-mini;
margin-bottom: $space-smaller;
@apply text-xs mb-1;
}
}
.sender--thumbnail {
@include round-corner();
height: $space-slab;
margin-right: $space-one;
margin-top: $space-micro;
width: $space-slab;
@apply h-3 mr-3 mt-0.5 w-3 rounded-full;
}
.activity-wrap {
background: var(--s-50);
border: 1px solid var(--s-100);
border-radius: var(--border-radius-medium);
display: flex;
font-size: var(--font-size-small);
justify-content: center;
margin: var(--space-smaller) 0;
padding: var(--space-smaller) var(--space-micro) var(--space-smaller)
var(--space-one);
@apply flex justify-center text-sm my-1 mx-0 py-1 pr-0.5 pl-2.5 bg-slate-50 dark:bg-slate-600 text-slate-800 dark:text-slate-100 rounded-md border border-slate-100 dark:border-slate-600 border-solid;
.is-text {
align-items: center;
display: inline-flex;
text-align: start;
@include breakpoint(xxxlarge up) {
display: flex;
}
@apply inline-flex items-center text-start 2xl:flex;
}
}
}
.activity-wrap .message-text__wrap {
.text-content p {
margin-bottom: 0;
@apply mb-0;
}
}
.conversation-footer {
display: flex;
flex-direction: column;
position: relative;
@apply flex relative flex-col;
}
.typing-indicator-wrap {
align-items: center;
display: flex;
height: 0;
position: absolute;
top: -$space-large;
width: 100%;
@apply items-center flex h-0 absolute w-full -top-8;
.typing-indicator {
@include elegant-card;
@include round-corner;
background: var(--white);
color: $color-light-gray;
font-size: $font-size-mini;
font-weight: $font-weight-bold;
margin: $space-one auto;
padding: $space-small $space-normal $space-small $space-two;
@apply py-2 pr-4 pl-5 bg-white dark:bg-slate-700 text-slate-800 dark:text-slate-100 text-xs font-semibold my-2.5 mx-auto;
.gif {
margin-left: $space-small;
width: $space-medium;
@apply ml-2 w-6;
}
}
}
@@ -358,16 +235,15 @@
h4,
h5,
h6 {
color: var(--color-body);
@apply text-slate-800 dark:text-slate-100;
}
a {
color: var(--color-woot);
text-decoration: underline;
@apply text-woot-500 dark:text-woot-500 underline;
}
p:last-child {
margin-bottom: 0;
@apply mb-0;
}
}
@@ -378,54 +254,14 @@
h4,
h5,
h6 {
color: var(--white);
@apply text-white dark:text-white;
}
a {
color: var(--white);
text-decoration: underline;
@apply text-white dark:text-white underline;
}
p:last-child {
margin-bottom: 0;
}
}
.conversation-sidebar-wrap {
flex: 0 0;
flex-basis: 100%;
height: auto;
overflow: auto;
z-index: var(--z-index-low);
@include breakpoint(medium up) {
flex-basis: 17.5rem;
}
@include breakpoint(large up) {
flex-basis: 18.75rem;
}
@include breakpoint(xlarge up) {
flex-basis: 19.375rem;
}
@include breakpoint(xxlarge up) {
flex-basis: 20.625rem;
}
@include breakpoint(xxxlarge up) {
flex-basis: 25rem;
}
}
.activity-wrap {
.message-text--metadata {
.time {
@include breakpoint(xlarge up) {
margin-left: var(--space-small);
}
}
@apply mb-0;
}
}
@@ -1,5 +1,29 @@
// scss-lint:disable QualifyingElement
label {
@apply text-slate-800 dark:text-slate-200;
}
textarea {
@apply bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
}
input {
@apply bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
&[disabled] {
@apply bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-400 border-slate-200 dark:border-slate-600;
}
}
input[type='file'] {
@apply bg-white dark:bg-slate-800;
}
select {
@apply bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
}
.error {
input[type='color'],
input[type='date'],
@@ -19,17 +43,11 @@
textarea,
select,
.multiselect > .multiselect__tags {
@include thin-border(var(--r-400));
@apply border border-solid border-red-400 dark:border-red-400;
}
.message {
color: var(--r-400);
display: block;
font-size: var(--font-size-small);
font-weight: $font-weight-normal;
margin-bottom: $space-one;
margin-top: -$space-normal;
width: 100%;
@apply text-red-400 dark:text-red-400 block text-sm mb-2.5 w-full;
}
}
@@ -42,26 +60,19 @@ input {
}
.input-wrap {
color: $color-heading;
font-size: $font-size-small;
font-weight: $font-weight-medium;
@apply text-slate-800 dark:text-slate-100 text-sm font-medium;
}
.help-text {
font-weight: $font-weight-normal;
@apply font-normal text-slate-600 dark:text-slate-400;
}
.input-group.small {
input {
font-size: var(--font-size-small);
height: var(--space-large);
@apply text-sm h-8;
}
.error {
border-color: var(--r-400);
@apply border-red-400 dark:border-red-400;
}
}
label {
@apply dark:text-slate-300;
}
@@ -1,116 +1,79 @@
@import '~dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins';
.modal-mask {
@include flex;
@include flex-align(center, middle);
background-color: $masked-bg;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9990;
// @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 {
position: absolute;
right: $space-small;
top: $space-small;
&:hover {
background: $color-background;
}
@apply absolute right-2 rtl:right-[unset] rtl:left-2 top-2;
}
.page-top-bar {
padding: $space-large $space-large $zero;
@apply px-8 pt-9 pb-0;
img {
max-height: 3.75rem;
@apply max-h-[3.75rem];
}
}
.modal-container {
@include normal-shadow;
border-radius: $space-smaller;
max-height: 100%;
overflow: auto;
position: relative;
width: 37.5rem;
@apply shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem];
&.medium {
max-width: 80%;
width: 56.25rem;
@apply max-w-[80%] w-[56.25rem];
}
.content-box {
height: auto;
padding: 0;
@apply h-auto p-0;
}
h2 {
color: $color-heading;
font-size: $font-size-medium;
font-weight: $font-weight-bold;
@apply text-slate-800 dark:text-slate-100 text-lg font-semibold;
}
p {
font-size: $font-size-small;
margin: 0;
padding: 0;
@apply text-sm m-0 p-0 text-slate-600 mt-2 text-sm dark:text-slate-300;
}
.content {
padding: $space-large;
@apply p-8;
}
form,
.modal-content {
align-self: center;
padding: $space-large;
@apply pt-4 pb-8 px-8 self-center;
a {
padding: $space-normal;
@apply p-4;
}
}
.modal-footer {
@include flex;
@include flex-align($x: flex-end, $y: middle);
padding: $space-small $zero;
.button {
margin-left: var(--space-small);
}
&:first-child {
.button {
margin-left: 0;
}
}
// @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 {
justify-content: end;
@apply justify-end;
}
}
.delete-item {
padding: $space-large;
@apply p-8;
button {
margin: 0;
@apply m-0;
}
}
}
.modal-enter,
.modal-leave {
opacity: 0;
@apply opacity-0;
}
.modal-enter .modal-container,
.modal-leave .modal-container {
transform: scale(1.1);
// @apply transform scale-110;
}
@@ -1,6 +1,6 @@
.reply-box {
transition: box-shadow 0.35s $swift-ease-out-function,
height 2s $swift-ease-out-function;
height 2s $swift-ease-out-function;
&.is-focused {
box-shadow: var(--shadow);
@@ -47,13 +47,13 @@
}
&.is-private {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
.reply-box__top {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
}
@@ -1,27 +1 @@
// EMPTY STATES
.empty-state {
padding: $space-jumbo $space-smaller;
.title,
.message {
display: block;
text-align: center;
width: 100%;
}
.title {
font-size: $font-size-giga;
font-weight: $font-weight-feather;
}
.message {
color: $color-gray;
margin: $space-normal auto;
width: 90%;
}
.button {
margin-top: $space-medium;
}
}
// To be removed
@@ -1,100 +1,69 @@
.tabs--container {
display: flex;
@apply flex;
}
.tabs--container--with-border {
@include border-normal-bottom;
@apply border-b border-slate-50 dark:border-slate-800/50;
}
.tabs {
border-left-width: 0;
border-right-width: 0;
border-top-width: 0;
display: flex;
min-width: var(--space-mega);
padding: 0 var(--space-normal);
@apply border-r-0 border-l-0 border-t-0 flex min-w-[6.25rem] py-0 px-4;
}
.tabs--with-scroll {
@apply overflow-hidden py-0 px-1;
max-width: calc(100% - 64px);
overflow: hidden;
padding: 0 var(--space-smaller);
}
.tabs--scroll-button {
align-items: center;
border-radius: 0;
cursor: pointer;
display: flex;
height: auto;
justify-content: center;
min-width: var(--space-large);
@apply items-center rounded-none cursor-pointer flex h-auto justify-center min-w-[2rem];
}
// Tab chat type
.tab--chat-type {
@include flex;
@apply flex;
.tabs-title {
a {
font-size: var(--font-size-default);
font-weight: var(--font-weight-medium);
padding-bottom: var(--space-slab);
padding-top: var(--space-slab);
@apply text-base font-medium py-3;
}
}
}
.tabs-title {
flex-shrink: 0;
margin: 0 var(--space-small);
@apply flex-shrink-0 my-0 mx-2 ;
.badge {
background: var(--color-background);
border-radius: var(--space-small);
color: var(--color-gray);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
margin: 0 var(--space-smaller);
padding: var(--space-smaller);
@apply bg-slate-50 dark:bg-slate-800 rounded-md text-slate-600 dark:text-slate-100 h-5 flex items-center justify-center text-xxs font-semibold my-0 mx-1 px-1 py-0;
}
&:first-child {
margin-left: 0;
@apply ml-0;
}
&:last-child {
margin-right: 0;
@apply mr-0;
}
&:hover,
&:focus {
a {
color: darken($medium-gray, 20%);
@apply text-slate-800 dark:text-slate-100;
}
}
a {
align-items: center;
border-bottom: 2px solid transparent;
color: $medium-gray;
display: flex;
flex-direction: row;
font-size: var(--font-size-small);
position: relative;
top: 1px;
@apply flex items-center flex-row border-b border-transparent text-slate-500 dark:text-slate-200 text-sm top-[1px] relative;
transition: border-color 0.15s $swift-ease-out-function;
}
&.is-active {
a {
border-bottom-color: var(--color-woot);
color: var(--color-woot);
@apply border-b border-woot-500 text-woot-500 dark:text-woot-500;
}
.badge {
background: $color-extra-light-blue;
color: var(--color-woot);
@apply bg-woot-50 dark:bg-woot-500 text-woot-500 dark:text-woot-50 dark:bg-opacity-40;
}
}
}
@@ -1,20 +1,19 @@
table {
border-spacing: 0;
font-size: var(--font-size-small);
@apply border-spacing-0 text-sm;
thead {
th {
@apply font-medium text-left uppercase text-slate-900 dark:text-slate-200;
@apply font-semibold tracking-[1px] text-left uppercase text-slate-900 dark:text-slate-200;
}
}
tbody {
tr {
@apply border-b border-slate-50 dark:border-slate-700;
@apply border-b border-slate-50 dark:border-slate-800/30;
}
td {
padding: var(--space-small);
@apply p-2.5 text-slate-700 dark:text-slate-100;
}
}
}
@@ -22,37 +21,68 @@ table {
.woot-table {
tr {
.show-if-hover {
opacity: 0;
transition: opacity 0.2s $swift-ease-out-function;
@apply opacity-0;
}
&:hover {
.show-if-hover {
opacity: 1;
@apply opacity-100;
}
}
}
.agent-name {
display: block;
font-weight: var(--font-weight-medium);
text-transform: capitalize;
@apply block font-medium capitalize;
}
.woot-thumbnail {
border-radius: 50%;
height: 3.125rem;
width: 3.125rem;
@apply rounded-full h-[3.125rem] w-[3.125rem];
}
.button-wrapper {
@include flex-align(left, null);
@include flex;
flex-direction: row;
min-width: 12.5rem;
@apply flex justify-start flex-row min-w-[12.5rem] gap-1;
}
.button {
margin: 0;
}
}
.ve-table {
.ve-table-container.ve-table-border-around {
@apply border-slate-200 dark:border-slate-700;
}
.ve-table-content {
.ve-table-header .ve-table-header-tr .ve-table-header-th {
@apply bg-slate-50 dark:bg-slate-800 text-slate-800 dark:text-slate-100 border-slate-100 dark:border-slate-700/50;
}
.ve-table-body .ve-table-body-tr .ve-table-body-td {
@apply bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-75 dark:border-slate-800;
}
.ve-table-body.ve-table-row-hover .ve-table-body-tr:hover td {
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100;
}
}
}
.table-pagination {
.ve-pagination-total {
@apply text-slate-600 dark:text-slate-200;
}
.ve-pagination-goto {
@apply text-slate-600 dark:text-slate-200;
.ve-pagination-goto-input {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200;
}
}
.ve-pagination-li {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200 border-slate-75 dark:border-slate-700;
}
}
@@ -1,15 +1,20 @@
<template>
<div class="cw-accordion">
<button class="cw-accordion--title drag-handle" @click="$emit('click')">
<div class="cw-accordion--title-wrap">
<emoji-or-icon class="icon-or-emoji" :icon="icon" :emoji="emoji" />
<h5>
<div class="-mt-px text-sm">
<button
class="flex items-center select-none w-full bg-slate-50 dark:bg-slate-800 border border-l-0 border-r-0 border-solid m-0 border-slate-100 dark:border-slate-700/50 cursor-grab justify-between py-2 px-4 drag-handle"
@click="$emit('click')"
>
<div class="flex justify-between mb-0.5">
<emoji-or-icon class="inline-block w-5" :icon="icon" :emoji="emoji" />
<h5
class="text-slate-800 text-sm dark:text-slate-100 mb-0 py-0 pr-2 pl-0"
>
{{ title }}
</h5>
</div>
<div class="button-icon--wrap">
<div class="flex flex-row">
<slot name="button" />
<div class="chevron-icon__wrap">
<div class="flex justify-end w-3 text-woot-500">
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
<fluent-icon v-else size="24" icon="add" type="solid" />
</div>
@@ -17,8 +22,8 @@
</button>
<div
v-if="isOpen"
class="cw-accordion--content"
:class="{ compact: compact }"
class="bg-white dark:bg-slate-900"
:class="compact ? 'p-0' : 'p-4'"
>
<slot />
</div>
@@ -56,67 +61,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.cw-accordion {
// This is done to fix contact sidebar border issues
// If you are using it else, find a fix to remove this hack
margin-top: -1px;
font-size: var(--font-size-small);
}
.cw-accordion--title {
align-items: center;
background: var(--s-50);
border-bottom: 1px solid var(--s-100);
border-top: 1px solid var(--s-100);
cursor: grab;
display: flex;
justify-content: space-between;
margin: 0;
padding: var(--space-small) var(--space-normal);
user-select: none;
width: 100%;
h5 {
font-size: var(--font-size-normal);
margin-bottom: 0;
padding: 0 var(--space-small) 0 0;
}
}
.cw-accordion--title-wrap {
display: flex;
justify-content: space-between;
margin-bottom: var(--space-micro);
}
.title-icon__wrap {
display: flex;
align-items: baseline;
}
.icon-or-emoji {
display: inline-block;
width: var(--space-two);
}
.button-icon--wrap {
display: flex;
flex-direction: row;
}
.chevron-icon__wrap {
display: flex;
justify-content: flex-end;
width: var(--space-slab);
color: var(--w-500);
}
.cw-accordion--content {
padding: var(--space-normal);
&.compact {
padding: 0;
}
}
</style>
@@ -1,5 +1,8 @@
<template>
<button class="small-6 medium-4 large-3 channel" @click="$emit('click')">
<button
class="sm:w-[50%] md:w-[34%] lg:w-[25%] channel"
@click="$emit('click')"
>
<img :src="src" :alt="title" />
<h3 class="channel__title">
{{ title }}
@@ -24,44 +27,36 @@ export default {
<style scoped lang="scss">
.inactive {
filter: grayscale(100%);
img {
filter: grayscale(100%);
}
&.channel:hover {
@apply border-transparent shadow-none cursor-not-allowed;
}
}
.channel {
background: var(--white);
border: 1px solid var(--color-border-light);
cursor: pointer;
display: flex;
flex-direction: column;
margin: -1px;
padding: var(--space-normal) 0;
transition: all 0.2s ease-in;
align-items: center;
@apply bg-white dark:bg-slate-900 cursor-pointer flex flex-col transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800;
&:hover {
border: 1px solid var(--w-500);
box-shadow: var(--shadow-medium);
z-index: var(--z-index-high);
@apply border-woot-500 dark:border-woot-500 shadow-md z-50;
}
&.disabled {
opacity: 0.6;
@apply opacity-60;
}
img {
margin: var(--space-normal) auto;
width: 50%;
@apply w-[50%] my-4 mx-auto;
}
.channel__title {
color: var(--color-body);
font-size: var(--font-size-default);
text-align: center;
text-transform: capitalize;
@apply text-slate-800 dark:text-slate-100 text-base text-center capitalize;
}
p {
color: var(--b-500);
width: 100%;
@apply text-slate-600 dark:text-slate-300 w-full text-sm;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div
class="conversations-list-wrap"
class="conversations-list-wrap flex-basis-clamp flex-shrink-0 flex-basis-custom overflow-hidden flex flex-col border-r rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
:class="{
hide: !showConversationList,
'list--full-width': isOnExpandedLayout,
@@ -8,26 +8,28 @@
>
<slot />
<div
class="chat-list__top"
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
class="flex items-center justify-between py-0 px-4"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700': hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex-center chat-list__title">
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="page-sub-title text-truncate margin-bottom-0"
class="text-xl break-words overflow-hidden whitespace-nowrap text-ellipsis text-black-900 dark:text-slate-100 mb-0"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="conversation--status-pill"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{
this.$t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`)
}}
</span>
</div>
<div class="filter--actions">
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
@@ -104,7 +106,10 @@
@chatTabChange="updateAssigneeTab"
/>
<p v-if="!chatListLoading && !conversationList.length" class="content-box">
<p
v-if="!chatListLoading && !conversationList.length"
class="overflow-auto p-4 flex justify-center items-center"
>
{{ $t('CHAT_LIST.LIST.404') }}
</p>
<conversation-bulk-actions
@@ -123,8 +128,8 @@
/>
<div
ref="activeConversation"
class="conversations-list"
:class="{ 'is-context-menu-open': isContextMenuOpen }"
class="conversations-list flex-1"
:class="{ 'overflow-hidden': isContextMenuOpen }"
>
<div>
<conversation-card
@@ -149,23 +154,20 @@
/>
</div>
<div v-if="chatListLoading" class="text-center">
<span class="spinner" />
<span class="spinner mt-4 mb-4" />
</div>
<woot-button
v-if="!hasCurrentPageEndReached && !chatListLoading"
variant="clear"
size="expanded"
class="text-center"
class="load-more--button"
@click="loadMoreConversations"
>
{{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }}
</woot-button>
<p
v-if="showEndOfListMessage"
class="text-center text-muted end-of-list-text"
>
<p v-if="showEndOfListMessage" class="text-center text-muted p-4">
{{ $t('CHAT_LIST.EOF') }}
</p>
</div>
@@ -956,70 +958,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
.spinner {
margin-top: var(--space-normal);
margin-bottom: var(--space-normal);
}
.conversations-list {
// Prevent the list from scrolling if the submenu is opened
&.is-context-menu-open {
overflow: hidden !important;
<style scoped>
@tailwind components;
@layer components {
.flex-basis-clamp {
flex-basis: clamp(20rem, 4vw + 21.25rem, 27.5rem);
}
}
</style>
<style scoped lang="scss">
.conversations-list-wrap {
flex-shrink: 0;
flex-basis: clamp(20rem, 4vw + 21.25rem, 27.5rem);
overflow: hidden;
&.hide {
display: none;
@apply hidden;
}
&.list--full-width {
flex-basis: 100%;
}
.page-sub-title {
font-size: var(--font-size-two);
@apply basis-full;
}
}
.filter--actions {
display: flex;
align-items: center;
gap: var(--space-smaller);
.conversations-list {
@apply overflow-hidden hover:overflow-y-auto;
}
.filter__applied {
padding-bottom: var(--space-slab) !important;
border-bottom: 1px solid var(--color-border);
.load-more--button {
@apply text-center rounded-none;
}
.tab--chat-type {
padding: 0 var(--space-normal);
@apply py-0 px-4;
::v-deep {
.tabs {
padding: 0;
@apply p-0;
}
}
}
.conversation--status-pill {
background: var(--color-background);
border-radius: var(--border-radius-small);
color: var(--color-medium-gray);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
margin: var(--space-micro) var(--space-small) 0;
padding: var(--space-smaller);
text-transform: capitalize;
}
.chat-list__title {
max-width: 85%;
}
</style>
@@ -1,7 +1,7 @@
<template>
<div class="custom-attribute">
<div class="title-wrap">
<h4 class="text-block-title title error">
<div class="py-3 px-4">
<div class="flex items-center mb-1">
<h4 class="text-block-title flex items-center m-0 w-full error">
<div v-if="isAttributeTypeCheckbox" class="checkbox-wrap">
<input
v-model="editedValue"
@@ -10,7 +10,7 @@
@change="onUpdate"
/>
</div>
<div class="name-button__wrap">
<div class="flex items-center justify-between w-full">
<span
class="attribute-name"
:class="{ error: $v.editedValue.$error }"
@@ -24,7 +24,7 @@
size="medium"
color-scheme="secondary"
icon="delete"
class-names="delete-button"
class-names="flex justify-end w-4"
@click="onDelete"
/>
</div>
@@ -47,7 +47,10 @@
<woot-button size="small" icon="checkmark" @click="onUpdate" />
</div>
</div>
<span v-if="shouldShowErrorMessage" class="error-message">
<span
v-if="shouldShowErrorMessage"
class="text-red-400 dark:text-red-500 text-sm block font-normal -mt-px w-full"
>
{{ errorMessage }}
</span>
</div>
@@ -61,14 +64,17 @@
:href="value"
target="_blank"
rel="noopener noreferrer"
class="value"
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
>
{{ urlValue }}
</a>
<p v-else class="value">
<p
v-else
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
>
{{ displayValue || '---' }}
</p>
<div class="action-buttons__wrap">
<div class="flex max-w-[2rem] gap-1 ml-1 rtl:mr-1 rtl:ml-0">
<woot-button
v-if="showActions"
v-tooltip="$t('CUSTOM_ATTRIBUTES.ACTIONS.COPY')"
@@ -269,98 +275,45 @@ export default {
</script>
<style lang="scss" scoped>
.custom-attribute {
padding: var(--space-slab) var(--space-normal);
}
.title-wrap {
display: flex;
align-items: center;
margin-bottom: var(--space-mini);
}
.title {
display: flex;
align-items: center;
margin: 0;
width: 100%;
}
.checkbox-wrap {
display: flex;
align-items: center;
@apply flex items-center;
}
.checkbox {
margin: 0 var(--space-small) 0 0;
}
.name-button__wrap {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
@apply my-0 mr-2 ml-0;
}
.attribute-name {
width: 100%;
@apply w-full text-slate-800 dark:text-slate-100;
&.error {
color: var(--r-400);
@apply text-red-400 dark:text-red-500;
}
}
.title--icon {
width: var(--space-two);
}
.edit-button {
display: none;
}
.delete-button {
display: flex;
justify-content: flex-end;
width: var(--space-normal);
@apply hidden;
}
.value--view {
display: flex;
@apply flex;
&.is-editable:hover {
.value {
background: var(--color-background);
margin-bottom: 0;
@apply bg-slate-50 dark:bg-slate-700 mb-0;
}
.edit-button {
display: block;
@apply block;
}
}
.action-buttons__wrap {
display: flex;
max-width: var(--space-larger);
}
}
.value {
display: inline-block;
min-width: var(--space-mega);
border-radius: var(--border-radius-small);
margin-bottom: 0;
word-break: break-all;
padding: var(--space-micro) var(--space-smaller);
}
.error-message {
color: var(--r-400);
display: block;
font-size: 0.875rem;
font-size: var(--font-size-small);
font-weight: 400;
margin-bottom: 0.625rem;
margin-top: -1rem;
width: 100%;
}
::v-deep {
.selector-wrap {
margin: 0;
top: var(--space-smaller);
@apply m-0 top-1;
.selector-name {
margin-left: 0;
@apply ml-0;
}
}
.name {
margin-left: 0;
@apply ml-0;
}
}
</style>
@@ -1,7 +1,7 @@
<template>
<div class="column">
<div class="flex flex-col">
<woot-modal-header :header-title="$t('CONVERSATION.CUSTOM_SNOOZE.TITLE')" />
<form class="row modal-content" @submit.prevent="chooseTime">
<form class="modal-content" @submit.prevent="chooseTime">
<date-picker
v-model="snoozeTime"
type="datetime"
@@ -11,7 +11,7 @@
:disabled-time="disabledTime"
:popup-style="{ width: '100%' }"
/>
<div class="modal-footer justify-content-end w-full">
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-button variant="clear" @click.prevent="onClose">
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
</woot-button>
@@ -64,12 +64,8 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.modal-footer {
padding: var(--space-two);
}
.modal-content {
padding: var(--space-small) var(--space-two) var(--space-zero);
@apply pt-2 px-5 pb-6;
}
</style>
@@ -1,5 +1,5 @@
<template>
<div class="column page-top-bar">
<div class="flex flex-col items-start pt-8 px-8 pb-0">
<img v-if="headerImage" :src="headerImage" alt="No image" />
<h2
ref="modalHeaderTitle"
@@ -10,10 +10,13 @@
<p
v-if="headerContent"
ref="modalHeaderContent"
class="small-12 column wrap-content"
class="w-full break-words text-slate-600 mt-2 text-sm dark:text-slate-300"
>
{{ headerContent }}
<span v-if="headerContentValue" class="content-value">
<span
v-if="headerContentValue"
class="font-semibold text-sm text-slate-600 dark:text-slate-300"
>
{{ headerContentValue }}
</span>
</p>
@@ -43,13 +46,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.wrap-content {
word-wrap: break-word;
margin-top: var(--space-small);
.content-value {
font-weight: var(--font-weight-bold);
}
}
</style>
@@ -1,20 +1,28 @@
<template>
<div class="row settings--section" :class="{ 'border-bottom': showBorder }">
<div class="medium-4 small-12 title--section">
<p v-if="title" class="sub-block-title">
<div
class="ml-0 mr-0 flex pt-0 pr-4 pb-4 pl-0"
:class="{
'pt-4 border-b border-solid border-slate-50 dark:border-slate-700/30': showBorder,
}"
>
<div class="w-[30%] min-w-0 max-w-[30%] pr-12">
<p
v-if="title"
class="text-base text-woot-500 dark:text-woot-500 mb-0 font-medium"
>
{{ title }}
</p>
<p class="sub-head">
<p class="text-sm mb-2">
<slot v-if="subTitle" name="subTitle">
{{ subTitle }}
</slot>
</p>
<p v-if="note">
<span class="note">{{ $t('INBOX_MGMT.NOTE') }}</span>
<span class="font-semibold">{{ $t('INBOX_MGMT.NOTE') }}</span>
{{ note }}
</p>
</div>
<div class="medium-6 small-12">
<div class="w-[50%] min-w-0 max-w-[50%]">
<slot />
</div>
</div>
@@ -42,31 +50,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.settings--section {
display: flex;
padding: 0 $space-normal $space-normal 0;
&.border-bottom {
padding-top: $space-normal;
border-bottom: 1px solid $color-border;
}
.sub-block-title {
color: $color-woot;
font-weight: $font-weight-medium;
margin-bottom: 0;
}
.title--section {
padding-right: var(--space-large);
}
.note {
font-weight: var(--font-weight-bold);
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<woot-button
size="small"
:size="size"
variant="clear"
color-scheme="secondary"
class="-ml-3 text-black-900 dark:text-slate-300"
@@ -13,6 +13,12 @@
import { BUS_EVENTS } from 'shared/constants/busEvents';
export default {
props: {
size: {
type: String,
default: 'small',
},
},
methods: {
onMenuItemClick() {
bus.$emit(BUS_EVENTS.TOGGLE_SIDEMENU);
@@ -1,25 +1,21 @@
<template>
<kbd class="hotkey">
<kbd class="hotkey p-0.5 min-w-[1rem] uppercase" :class="customClass">
<slot />
</kbd>
</template>
<script>
export default {
props: {
customClass: {
type: String,
default: '',
},
},
};
</script>
<style lang="scss">
kbd.hotkey {
display: inline-flex;
flex-shrink: 0;
align-items: center;
user-select: none;
justify-content: center;
padding: var(--space-micro);
min-width: var(--space-normal);
font-size: var(--font-size-micro);
border-radius: var(--space-smaller);
background-color: var(--color-background);
border: 1px solid var(--color-border);
text-transform: uppercase;
color: var(--color-body);
letter-spacing: var(--space-micro);
line-height: var(--font-size-micro);
@apply inline-flex leading-[0.625rem] rounded tracking-wide flex-shrink-0 items-center select-none justify-center;
}
</style>
@@ -8,7 +8,7 @@
>
<fluent-icon v-if="!!iconClass" :icon="iconClass" class="icon" />
<span>{{ buttonText }}</span>
<spinner v-if="loading" />
<spinner v-if="loading" class="ml-2" :color-scheme="spinnerClass" />
</button>
</template>
@@ -40,6 +40,10 @@ export default {
type: String,
default: '',
},
spinnerClass: {
type: String,
default: '',
},
type: {
type: String,
default: 'submit',
@@ -47,7 +51,7 @@ export default {
},
computed: {
computedClass() {
return `button nice ${this.buttonClass || ' '}`;
return `button nice gap-2 ${this.buttonClass || ' '}`;
},
},
methods: {
@@ -59,11 +63,9 @@ export default {
</script>
<style lang="scss" scoped>
button:disabled {
opacity: 1;
background-color: var(--w-100);
@apply bg-woot-100 dark:bg-woot-500/25 dark:text-slate-500 opacity-100;
&:hover {
background-color: var(--w-100);
@apply bg-woot-100 dark:bg-woot-500/25;
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<div class="resolve-actions">
<div class="resolve-actions relative flex items-center justify-end">
<div class="button-group">
<woot-button
v-if="isOpen"
@@ -251,23 +251,11 @@ export default {
};
</script>
<style lang="scss" scoped>
.resolve-actions {
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
}
.dropdown-pane {
left: unset;
top: 2.625rem;
margin-top: var(--space-micro);
right: 0;
max-width: 12.5rem;
min-width: 9.75rem;
@apply left-auto top-[2.625rem] mt-0.5 right-0 max-w-[12.5rem] min-w-[9.75rem];
.dropdown-menu__item {
margin-bottom: 0;
@apply mb-0;
}
}
</style>
@@ -29,6 +29,7 @@ const settings = accountId => ({
'settings_inboxes_page_channel',
'settings_integrations_dashboard_apps',
'settings_integrations_integration',
'settings_integrations_slack',
'settings_integrations_webhook',
'settings_integrations',
'settings_teams_add_agents',
@@ -92,7 +93,6 @@ const settings = accountId => ({
{
icon: 'automation',
label: 'AUTOMATION',
beta: true,
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
@@ -101,7 +101,6 @@ const settings = accountId => ({
{
icon: 'bot',
label: 'AGENT_BOTS',
beta: true,
hasSubMenu: false,
globalConfigFlag: 'csmlEditorHost',
toState: frontendURL(`accounts/${accountId}/settings/agent-bots`),
@@ -114,7 +113,6 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/macros`),
toStateName: 'macros_wrapper',
beta: true,
featureFlag: FEATURE_FLAGS.MACROS,
},
{
@@ -1,18 +1,20 @@
<template>
<div
v-if="showShowCurrentAccountContext"
class="rounded-md text-xs py-2 px-2 mt-2 relative border border-slate-50 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700 cursor-pointer"
class="text-slate-700 dark:text-slate-200 rounded-md text-xs py-2 px-2 mt-2 relative border border-slate-50 dark:border-slate-800/50 hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer"
@mouseover="setShowSwitch"
@mouseleave="resetShowSwitch"
>
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="text-ellipsis font-medium mb-0">
<p
class="text-ellipsis overflow-hidden whitespace-nowrap font-medium mb-0 text-slate-800 dark:text-slate-100"
>
{{ account.name }}
</p>
<transition name="fade">
<div
v-if="showSwitchButton"
class="bg-gradient-to-r rtl:bg-gradient-to-l from-transparent via-white dark:via-slate-700 to-white dark:to-slate-700 flex items-center h-full justify-end absolute top-0 right-0 w-full"
class="ltr:overlay-shadow ltr:dark:overlay-shadow-dark rtl:rtl-overlay-shadow rtl:dark:rtl-overlay-shadow-dark flex items-center h-full rounded-md justify-end absolute top-0 right-0 w-full"
>
<div class="my-0 mx-2">
<woot-button
@@ -54,7 +56,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
<style scoped>
@tailwind components;
@layer components {
.overlay-shadow {
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
}
.overlay-shadow-dark {
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
rgb(21, 23, 24) 50%
);
}
.rtl-overlay-shadow {
background-image: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
}
.rtl-overlay-shadow-dark {
background-image: linear-gradient(
to left,
rgba(0, 0, 0, 0) 0%,
rgb(21, 23, 24) 50%
);
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
@@ -62,6 +98,6 @@ export default {
.fade-enter,
.fade-leave-to {
opacity: 0;
@apply opacity-0;
}
</style>
@@ -2,24 +2,23 @@
<woot-modal
:show="show"
:on-close="() => $emit('close-account-create-modal')"
class="account-selector--modal"
>
<div class="column content-box">
<div class="h-auto overflow-auto flex flex-col">
<woot-modal-header
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
/>
<div v-if="!hasAccounts" class="alert-wrap">
<div class="callout alert">
<div class="icon-wrap">
<div v-if="!hasAccounts" class="text-sm mt-6 mx-8 mb-0">
<div class="items-center rounded-md flex alert">
<div class="ml-1 mr-3">
<fluent-icon icon="warning" />
</div>
{{ $t('CREATE_ACCOUNT.NO_ACCOUNT_WARNING') }}
</div>
</div>
<form class="row" @submit.prevent="addAccount">
<div class="medium-12 columns">
<form class="flex flex-col w-full" @submit.prevent="addAccount">
<div class="w-full">
<label :class="{ error: $v.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
@@ -30,8 +29,8 @@
/>
</label>
</div>
<div class="modal-footer medium-12 columns">
<div class="medium-12 columns">
<div class="w-full">
<div class="w-full">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
@@ -102,20 +101,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.alert-wrap {
font-size: var(--font-size-small);
margin: var(--space-medium) var(--space-large) var(--space-zero);
.callout {
align-items: center;
border-radius: var(--border-radius-normal);
display: flex;
}
}
.icon-wrap {
margin-left: var(--space-smaller);
margin-right: var(--space-slab);
}
</style>
@@ -1,7 +1,7 @@
<template>
<div class="mb-4">
<button
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-800 dark:hover:text-slate-100 hover:text-slate-600 relative"
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
:class="{
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50': isNotificationPanelActive,
}"
@@ -3,7 +3,7 @@
<div
v-if="show"
v-on-clickaway="onClickAway"
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-10 rounded-md shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-30 rounded-md shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
:class="{ 'block visible': show }"
>
<availability-status />
@@ -60,6 +60,17 @@
</a>
</router-link>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="appearance"
@click="openAppearanceOptions"
>
{{ $t('SIDEBAR_ITEMS.APPEARANCE') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
<a
href="/super_admin"
@@ -145,6 +156,10 @@ export default {
onClickAway() {
if (this.show) this.$emit('close');
},
openAppearanceOptions() {
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'appearance_settings' });
},
},
};
</script>
@@ -1,6 +1,6 @@
<template>
<div
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-700 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-800/50 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
>
<div class="flex flex-col items-center">
<logo
@@ -46,6 +46,7 @@ import AgentDetails from './AgentDetails';
import NotificationBell from './NotificationBell';
import wootConstants from 'dashboard/constants/globals';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { ACCOUNT_EVENTS } from '../../../helper/AnalyticsHelper/events';
export default {
components: {
@@ -99,6 +100,7 @@ export default {
window.$chatwoot.toggle();
},
openNotificationPanel() {
this.$track(ACCOUNT_EVENTS.OPENED_NOTIFICATIONS);
this.$emit('open-notification-panel');
},
},
@@ -3,7 +3,7 @@
<a
v-tooltip.right="$t(`SIDEBAR.${name}`)"
:href="href"
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-800 dark:hover:text-slate-100 hover:text-slate-600 relative"
class="text-slate-700 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
:class="{
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
isActive || isChildMenuActive,
@@ -1,7 +1,7 @@
<template>
<div
v-if="hasSecondaryMenu"
class="h-full overflow-auto w-48 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-700 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
class="h-full overflow-auto w-48 flex flex-col bg-white dark:bg-slate-900 border-r dark:border-slate-800/50 rtl:border-r-0 rtl:border-l border-slate-50 text-sm px-2 pb-8"
>
<account-context @toggle-accounts="toggleAccountModal" />
<transition-group
@@ -22,7 +22,7 @@
class="inline-flex items-center justify-center w-4 rounded-sm bg-slate-100 dark:bg-slate-700 p-0.5 mr-1.5 rtl:mr-0 rtl:ml-1.5"
>
<fluent-icon
class="text-xxs text-slate-600 dark:text-slate-200"
class="text-xxs text-slate-700 dark:text-slate-200"
:class="{
'text-woot-500 dark:text-woot-500': isActive,
}"
@@ -41,7 +41,7 @@
>
<span
:title="menuTitle"
class="text-sm text-slate-600 dark:text-slate-100"
class="text-sm text-slate-700 dark:text-slate-100"
:class="{
'text-woot-500 dark:text-woot-500': isActive,
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
@@ -54,8 +54,8 @@
class="bg-slate-50 dark:bg-slate-700 rounded text-xxs font-medium mx-1 py-0 px-1"
:class="
isCountZero
? `text-slate-300 dark:text-slate-600`
: `text-slate-600 dark:text-slate-50`
? `text-slate-300 dark:text-slate-700`
: `text-slate-700 dark:text-slate-50`
"
>
{{ childItemCount }}
@@ -114,7 +114,9 @@ export default {
},
computed: {
showIcon() {
return { 'text-truncate': this.shouldTruncate };
return {
'overflow-hidden whitespace-nowrap text-ellipsis': this.shouldTruncate,
};
},
isCountZero() {
return this.childItemCount === 0;
@@ -1,5 +1,8 @@
<template>
<div class="banner" :class="bannerClasses">
<div
class="banner flex items-center h-12 gap-4 text-white dark:text-white text-xs py-3 px-4 justify-center"
:class="bannerClasses"
>
<span class="banner-message">
{{ bannerMessage }}
<a
@@ -96,87 +99,65 @@ export default {
<style lang="scss" scoped>
.banner {
--x-padding: var(--space-normal);
--y-padding: var(--space-slab);
display: flex;
gap: var(--x-padding);
color: var(--white);
font-size: var(--font-size-mini);
padding: var(--y-padding) var(--x-padding);
justify-content: center;
&.primary {
background: var(--w-500);
@apply bg-woot-500 dark:bg-woot-500;
.banner-action__button {
background: var(--w-600);
border: none;
color: var(--white);
@apply bg-woot-600 dark:bg-woot-600 border-none text-white;
&:hover {
background: var(--w-800);
@apply bg-woot-700 dark:bg-woot-700;
}
}
}
&.secondary {
background: var(--s-200);
color: var(--s-800);
@apply bg-slate-200 dark:bg-slate-300 text-slate-800 dark:text-slate-800;
a {
color: var(--s-800);
@apply text-slate-800 dark:text-slate-800;
}
}
&.alert {
background: var(--r-500);
@apply bg-red-500 dark:bg-red-500;
.banner-action__button {
background: var(--r-700);
border: none;
color: var(--white);
@apply bg-red-700 dark:bg-red-700 border-none text-white dark:text-white;
&:hover {
background: var(--r-800);
@apply bg-red-800 dark:bg-red-800;
}
}
}
&.warning {
background: var(--y-600);
color: var(--y-500);
@apply bg-yellow-500 dark:bg-yellow-500 text-yellow-500 dark:text-yellow-500;
a {
color: var(--y-500);
@apply text-yellow-500 dark:text-yellow-500;
}
}
&.gray {
background: var(--b-500);
@apply text-black-500 dark:text-black-500;
.banner-action__button {
color: var(--white);
@apply text-white dark:text-white;
}
}
a {
margin-left: var(--space-smaller);
text-decoration: underline;
color: var(--white);
font-size: var(--font-size-mini);
@apply ml-1 underline text-white dark:text-white text-xs;
}
.banner-action__button {
::v-deep .button__content {
white-space: nowrap;
@apply whitespace-nowrap;
}
}
.banner-message {
display: flex;
align-items: center;
@apply flex items-center;
}
.actions {
display: flex;
gap: var(--space-smaller);
right: var(--y-padding);
@apply flex gap-1 right-3;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div
class="context-menu-container"
class="fixed outline-none z-[9999] cursor-pointer"
:style="style"
tabindex="0"
@blur="$emit('close')"
@@ -40,11 +40,3 @@ export default {
},
};
</script>
<style>
.context-menu-container {
position: fixed;
z-index: var(--z-index-very-high);
outline: none;
cursor: pointer;
}
</style>
@@ -1,7 +1,7 @@
<template>
<div :class="labelClass" :style="labelStyle" :title="description">
<span v-if="icon" class="label-action--button">
<fluent-icon :icon="icon" size="12" class="label--icon" />
<fluent-icon :icon="icon" size="12" class="label--icon cursor-pointer" />
</span>
<span
v-if="['smooth', 'dashed'].includes(variant) && title && !icon"
@@ -103,145 +103,98 @@ export default {
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/variables';
.label {
display: inline-flex;
align-items: center;
font-weight: var(--font-weight-medium);
gap: var(--space-smaller);
margin-right: var(--space-smaller);
margin-bottom: var(--space-smaller);
padding: var(--space-smaller);
background: var(--s-50);
color: var(--s-800);
border: 1px solid var(--s-75);
height: var(--space-medium);
@apply inline-flex items-center font-medium gap-1 mr-1 rtl:ml-1 rtl:mr-0 mb-1 p-1 bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100 border border-solid border-slate-75 dark:border-slate-600 h-6;
&.small {
font-size: var(--font-size-mini);
padding: var(--space-micro) var(--space-smaller);
line-height: 1.2;
height: var(--space-two);
}
.label--icon {
cursor: pointer;
@apply text-xs py-0.5 px-1 leading-tight h-5;
}
&.small .label--icon,
&.small .close--icon {
font-size: var(--font-size-nano);
@apply text-[0.5rem];
}
a {
font-size: var(--font-size-mini);
@apply text-xs;
&:hover {
text-decoration: underline;
@apply underline;
}
}
/* Color Schemes */
&.primary {
background: var(--w-100);
color: var(--w-900);
border: 1px solid var(--w-200);
@apply bg-woot-100 dark:bg-woot-100 text-woot-900 dark:text-woot-900 border border-solid border-woot-200;
a {
color: var(--w-900);
@apply text-woot-900 dark:text-woot-900;
}
.label-color-dot {
background: var(--w-600);
@apply bg-woot-600 dark:bg-woot-600;
}
}
&.secondary {
background: var(--s-100);
color: var(--s-900);
border: 1px solid var(--s-200);
@apply bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-slate-100 border border-solid border-slate-200 dark:border-slate-600;
a {
color: var(--s-900);
@apply text-slate-900 dark:text-slate-100;
}
.label-color-dot {
background: var(--s-600);
@apply bg-slate-600 dark:bg-slate-600;
}
}
&.success {
background: var(--g-100);
color: var(--g-900);
border: 1px solid var(--g-200);
@apply bg-green-100 dark:bg-green-700 text-green-900 dark:text-green-100 border border-solid border-green-200 dark:border-green-600;
a {
color: var(--g-900);
@apply text-green-900 dark:text-green-100;
}
.label-color-dot {
background: var(--g-600);
@apply bg-green-600 dark:bg-green-600;
}
}
&.alert {
background: var(--r-100);
color: var(--r-900);
border: 1px solid var(--r-200);
@apply bg-red-100 dark:bg-red-700 text-red-900 dark:text-red-100 border border-solid border-red-200 dark:border-red-600;
a {
color: var(--r-900);
@apply text-red-900 dark:text-red-100;
}
.label-color-dot {
background: var(--r-600);
@apply bg-red-600 dark:bg-red-600;
}
}
&.warning {
background: var(--y-100);
color: var(--y-900);
border: 1px solid var(--y-200);
@apply bg-yellow-100 dark:bg-yellow-700 text-yellow-900 dark:text-yellow-100 border border-solid border-yellow-200 dark:border-yellow-600;
a {
color: var(--y-900);
@apply text-yellow-900 dark:text-yellow-100;
}
.label-color-dot {
background: var(--y-900);
@apply bg-yellow-900 dark:bg-yellow-900;
}
}
&.smooth {
background: transparent;
color: var(--s-700);
border: 1px solid var(--s-100);
@apply bg-transparent text-slate-700 dark:text-slate-100 border border-solid border-slate-100 dark:border-slate-700;
}
&.dashed {
background: transparent;
color: var(--s-700);
border: 1px dashed var(--s-100);
@apply bg-transparent text-slate-700 dark:text-slate-100 border border-dashed border-slate-100 dark:border-slate-700;
}
}
.label-close--button {
color: var(--s-800);
margin-bottom: var(--space-minus-micro);
border-radius: var(--border-radius-small);
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
&:hover {
background: var(--s-100);
}
@apply text-slate-800 dark:text-slate-100 -mb-0.5 rounded-sm cursor-pointer flex items-center justify-center hover:bg-slate-100 dark:hover:bg-slate-700;
}
.label-action--button {
display: flex;
margin-right: var(--space-smaller);
@apply flex mr-1;
}
.label-color-dot {
display: inline-block;
width: var(--space-slab);
height: var(--space-slab);
border-radius: var(--border-radius-small);
box-shadow: var(--shadow-small);
@apply inline-block w-3 h-3 rounded-sm shadow-sm;
}
.label.small .label-color-dot {
width: var(--space-small);
height: var(--space-small);
border-radius: var(--border-radius-small);
box-shadow: var(--shadow-small);
@apply w-2 h-2 rounded-sm shadow-sm;
}
</style>
@@ -1,20 +1,30 @@
<template>
<div class="preview-card--wrap" :class="{ activecard: active }">
<div class="preview-card--wrap" :class="{ 'active-card': active }">
<div class="header--wrap" :class="{ active: active }">
<div class="heading-wrap text-block-title">{{ heading }}</div>
<div class="items-center flex font-medium p-1 text-sm">{{ heading }}</div>
<fluent-icon
v-if="active"
icon="checkmark-circle"
type="solid"
size="24"
class="checkmark"
class="text-woot-500 dark:text-woot-500"
/>
</div>
<div class="content-wrap">
<div
class="text-slate-700 dark:text-slate-200 text-xs leading-[1.4] px-3 pt-3 pb-0 text-start"
>
{{ content }}
</div>
<div v-if="src" class="image-wrap">
<img :src="src" class="image" :class="{ activeimage: active }" />
<div v-if="src" class="p-3">
<img
:src="src"
class="border border-solid rounded-md"
:class="
active
? 'border-woot-75 dark:border-woot-700'
: 'border-slate-50 dark:border-slate-600'
"
/>
</div>
<slot v-else />
</div>
@@ -49,66 +59,18 @@ export default {
<style lang="scss" scoped>
.preview-card--wrap {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
max-height: 21.25rem;
max-width: 23.75rem;
min-width: 15rem;
@apply flex flex-col min-w-[15rem] max-h-[21.25rem] max-w-[23.75rem] rounded-md border border-solid border-slate-75 dark:border-slate-600;
.header--wrap {
background: var(--s-50);
border-bottom: 1px solid var(--color-border);
border-top-left-radius: var(--border-radius-normal);
border-top-right-radius: var(--border-radius-normal);
display: flex;
height: 2.5rem;
justify-content: space-between;
padding: var(--space-small);
width: 100%;
@apply flex justify-between items-center px-2 w-full h-10 bg-slate-50 dark:bg-slate-900 rounded-t-[5px] border-b border-solid border-slate-50 dark:border-slate-600;
}
.active {
background: var(--w-50);
border-bottom: 1px solid var(--w-75);
}
.heading-wrap {
align-items: center;
display: flex;
font-weight: var(--font-weight-medium);
padding: var(--space-smaller);
}
.checkmark {
color: var(--w-500);
}
.content-wrap {
color: var(--s-700);
font-size: var(--font-size-mini);
line-height: 1.4;
padding: var(--space-slab) var(--space-slab) 0 var(--space-slab);
text-align: start;
}
.image-wrap {
padding: var(--space-slab);
}
.image {
border: 1px solid var(--color-border);
border-radius: var(--border-radius-normal);
}
.activeimage {
border: 1px solid var(--w-75);
@apply bg-woot-50 border-b border-solid border-woot-75 dark:border-woot-700;
}
}
.activecard {
background: var(--w-25);
border: 1px solid var(--w-300);
.active-card {
@apply bg-woot-25 dark:bg-slate-700 border border-solid border-woot-300 dark:border-woot-400;
}
</style>
@@ -26,10 +26,10 @@ export default {
</script>
<style lang="scss" scoped>
.toggle-button {
@apply bg-slate-200 dark:bg-slate-600;
--toggle-button-box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px,
rgba(59, 130, 246, 0.5) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px,
rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
background-color: var(--s-200);
border-radius: var(--border-radius-large);
border: 2px solid transparent;
cursor: pointer;
@@ -61,8 +61,8 @@ export default {
}
span {
@apply bg-white dark:bg-slate-900;
--space-one-point-five: 0.9375rem;
background-color: var(--white);
border-radius: 100%;
box-shadow: var(--toggle-button-box-shadow);
display: inline-block;
@@ -7,9 +7,11 @@
>
<a @click="onTabClick">
{{ name }}
<span v-if="showBadge" class="badge">
{{ getItemCount }}
</span>
<div v-if="showBadge" class="badge">
<span>
{{ getItemCount }}
</span>
</div>
</a>
</li>
</template>
@@ -5,7 +5,7 @@
delay: { show: 1500, hide: 0 },
hideOnClick: true,
}"
class="text-xxs text-black-600 leading-4 ml-auto hover:text-black-900"
class="text-xxs text-slate-500 dark:text-slate-500 leading-4 ml-auto hover:text-slate-900 dark:hover:text-slate-100"
>
<span>{{ `${createdAtTime} • ${lastActivityTime}` }}</span>
</div>
@@ -11,8 +11,10 @@
class="item"
:class="{ active: isActive(item), over: isOver(item) }"
>
<div class="flex-align-center">
<h3 class="text-truncate">
<div class="flex items-center">
<h3
class="overflow-hidden whitespace-nowrap text-ellipsis leading-tight"
>
{{ item.title }}
</h3>
<span v-if="isOver(item)" class="completed">
@@ -42,7 +44,7 @@ export default {
},
computed: {
classObject() {
return 'full-width';
return 'w-full';
},
activeIndex() {
return this.items.findIndex(i => i.route === this.$route.name);
@@ -1,41 +1,60 @@
<template>
<div v-if="isAIIntegrationEnabled" class="position-relative">
<woot-button
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
@click="openAIAssist"
/>
<woot-modal
:show.sync="showAIAssistanceModal"
:on-close="hideAIAssistanceModal"
>
<AIAssistanceModal
:ai-option="aiOption"
@apply-text="insertText"
@close="hideAIAssistanceModal"
<div v-if="!isFetchingAppIntegrations">
<div v-if="isAIIntegrationEnabled" class="relative">
<AIAssistanceCTAButton
v-if="shouldShowAIAssistCTAButton"
@click="openAIAssist"
/>
</woot-modal>
<woot-button
v-else
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
@click="openAIAssist"
/>
<woot-modal
:show.sync="showAIAssistanceModal"
:on-close="hideAIAssistanceModal"
>
<AIAssistanceModal
:ai-option="aiOption"
@apply-text="insertText"
@close="hideAIAssistanceModal"
/>
</woot-modal>
</div>
<div v-else-if="shouldShowAIAssistCTAButtonForAdmin" class="relative">
<AIAssistanceCTAButton @click="openAICta" />
<woot-modal :show.sync="showAICtaModal" :on-close="hideAICtaModal">
<AICTAModal @close="hideAICtaModal" />
</woot-modal>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import AICTAModal from './AICTAModal.vue';
import AIAssistanceModal from './AIAssistanceModal.vue';
import aiMixin from 'dashboard/mixins/aiMixin';
import adminMixin from 'dashboard/mixins/aiMixin';
import aiMixin from 'dashboard/mixins/isAdmin';
import { CMD_AI_ASSIST } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
export default {
components: {
AIAssistanceModal,
AICTAModal,
AIAssistanceCTAButton,
},
mixins: [aiMixin, eventListenerMixins],
mixins: [aiMixin, eventListenerMixins, adminMixin, uiSettingsMixin],
data: () => ({
showAIAssistanceModal: false,
showAICtaModal: false,
aiOption: '',
initialMessage: '',
}),
@@ -43,6 +62,21 @@ export default {
...mapGetters({
currentChat: 'getSelectedChat',
}),
isAICTAModalDismissed() {
return this.uiSettings.is_open_ai_cta_modal_dismissed;
},
// Display a AI CTA button for admins if the AI integration has not been added yet and the AI assistance modal has not been dismissed.
shouldShowAIAssistCTAButtonForAdmin() {
return (
this.isAdmin &&
!this.isAIIntegrationEnabled &&
!this.isAICTAModalDismissed
);
},
// Display a AI CTA button for agents and other admins who have not yet opened the AI assistance modal.
shouldShowAIAssistCTAButton() {
return this.isAIIntegrationEnabled && !this.isAICTAModalDismissed;
},
},
mounted() {
@@ -61,13 +95,28 @@ export default {
}
},
hideAIAssistanceModal() {
this.recordAnalytics('DISMISS_AI_SUGGESTION', {
aiOption: this.aiOption,
});
this.showAIAssistanceModal = false;
},
openAIAssist() {
// Dismiss the CTA modal if it is not dismissed
if (!this.isAICTAModalDismissed) {
this.updateUISettings({
is_open_ai_cta_modal_dismissed: true,
});
}
this.initialMessage = this.draftMessage;
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'ai_assist' });
},
hideAICtaModal() {
this.showAICtaModal = false;
},
openAICta() {
this.showAICtaModal = true;
},
onAIAssist(option) {
this.aiOption = option;
this.showAIAssistanceModal = true;
@@ -0,0 +1,99 @@
<template>
<div class="relative">
<woot-button
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
class-names="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
@click="onClick"
>
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST') }}
</woot-button>
<div
class="radar-ping-animation absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500"
/>
<div
class="absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500 opacity-50"
/>
</div>
</template>
<script>
export default {
methods: {
onClick() {
this.$emit('click');
},
},
};
</script>
<style scoped>
@tailwind components;
@layer components {
/* Gradient animation */
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.cta-btn {
animation: gradient 5s ease infinite;
border: 0;
}
.cta-btn-light {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #f2f5f8, #f2f5f8);
}
.cta-btn-dark {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #313538, #313538);
}
.cta-btn-light-hover {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #e3e5e7, #e3e5e7);
}
.cta-btn-dark-hover {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #202425, #202425);
}
/* Radar ping animation */
@keyframes ping {
75%,
100% {
transform: scale(2);
opacity: 0;
}
}
.radar-ping-animation {
animation: ping 1s ease infinite;
}
}
</style>
@@ -1,13 +1,16 @@
<template>
<div class="column">
<div class="flex flex-col">
<woot-modal-header :header-title="headerTitle" />
<form class="row modal-content" @submit.prevent="applyText">
<form
class="modal-content flex flex-col w-full"
@submit.prevent="applyText"
>
<div v-if="draftMessage" class="w-full">
<h4 class="sub-block-title margin-top-1 ">
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.DRAFT_TITLE') }}
</h4>
<p v-dompurify-html="formatMessage(draftMessage, false)" />
<h4 class="sub-block-title margin-top-1">
<h4 class="text-base mt-1 text-slate-700 dark:text-slate-100">
{{
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.GENERATED_TITLE')
}}
@@ -18,7 +21,7 @@
<p v-else v-dompurify-html="formatMessage(generatedContent, false)" />
</div>
<div class="modal-footer justify-content-end w-full">
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-button variant="clear" @click.prevent="onClose">
{{
$t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.BUTTONS.CANCEL')
@@ -98,7 +101,7 @@ export default {
<style lang="scss" scoped>
.modal-content {
padding-top: var(--space-small);
@apply pt-2 px-8 pb-8;
}
.container {
@@ -0,0 +1,108 @@
<template>
<div class="px-0 min-w-0 flex-1">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.TITLE')"
:header-content="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DESC')"
/>
<form
class="flex flex-wrap flex-col modal-content"
@submit.prevent="finishOpenAI"
>
<div class="mt-2 w-full">
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:placeholder="
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
"
@blur="$v.value.$touch"
/>
</div>
<div class="flex flex-row justify-between gap-2 py-2 px-0 w-full">
<woot-button variant="link" @click.prevent="openOpenAIDoc">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.NEED_HELP') }}
</woot-button>
<div class="flex items-center gap-1">
<woot-button variant="clear" @click.prevent="onDismiss">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
</woot-button>
<woot-button :is-disabled="$v.value.$invalid">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
</woot-button>
</div>
</div>
</form>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import aiMixin from 'dashboard/mixins/aiMixin';
import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
export default {
mixins: [aiMixin, alertMixin, uiSettingsMixin],
data() {
return {
value: '',
};
},
validations: {
value: {
required,
},
},
computed: {
...mapGetters({
appIntegrations: 'integrations/getAppIntegrations',
}),
},
methods: {
onClose() {
this.$emit('close');
},
onDismiss() {
this.showAlert(
this.$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DISMISS_MESSAGE')
);
this.updateUISettings({
is_open_ai_cta_modal_dismissed: true,
});
this.onClose();
},
async finishOpenAI() {
const payload = {
app_id: 'openai',
settings: {
api_key: this.value,
},
};
try {
await this.$store.dispatch('integrations/createHook', payload);
this.alertMessage = this.$t(
'INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.SUCCESS_MESSAGE'
);
this.recordAnalytics(
OPEN_AI_EVENTS.ADDED_AI_INTEGRATION_VIA_CTA_BUTTON
);
this.onClose();
} catch (error) {
const errorMessage = error?.response?.data?.message;
this.alertMessage =
errorMessage || this.$t('INTEGRATION_APPS.ADD.API.ERROR_MESSAGE');
} finally {
this.showAlert(this.alertMessage);
}
},
openOpenAIDoc() {
window.open('https://www.chatwoot.com/blog/v2-17', '_blank');
},
},
};
</script>
@@ -1,29 +1,37 @@
<template>
<div class="preview-item__wrap">
<div
class="preview-item__wrap flex flex-col overflow-auto mt-4 max-h-[12.5rem]"
>
<div
v-for="(attachment, index) in attachments"
:key="attachment.id"
class="preview-item"
class="preview-item flex p-1 bg-slate-50 dark:bg-slate-800 rounded-md w-[15rem] mb-1"
>
<div class="thumb-wrap">
<div class="max-w-[4rem] flex-shrink-0 w-6 flex items-center">
<img
v-if="isTypeImage(attachment.resource)"
class="image-thumb"
:src="attachment.thumb"
/>
<span v-else class="attachment-thumb"> 📄 </span>
<span v-else class="w-6 h-6 text-lg relative -top-px text-left">
📄
</span>
</div>
<div class="file-name-wrap">
<span class="item">
<div class="max-w-[60%] min-w-[50%] overflow-hidden text-ellipsis ml-2">
<span
class="h-4 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap"
>
{{ fileName(attachment.resource) }}
</span>
</div>
<div class="file-size-wrap">
<span class="item text-truncate">
<div class="w-[30%] justify-center">
<span
class="item overflow-hidden text-xs text-ellipsis whitespace-nowrap"
>
{{ formatFileSize(attachment.resource) }}
</span>
</div>
<div class="remove-file-wrap">
<div class="flex items-center justify-center">
<woot-button
v-if="!isTypeAudio(attachment.resource)"
class="remove--attachment clear secondary"
@@ -70,103 +78,20 @@ export default {
};
</script>
<style lang="scss" scoped>
.preview-item__wrap {
display: flex;
flex-direction: column;
overflow: auto;
margin-top: var(--space-normal);
max-height: 12.5rem;
}
.preview-item {
display: flex;
padding: var(--space-slab) 0 0;
background: var(--color-background-light);
background: var(--b-50);
border-radius: var(--border-radius-normal);
width: 15rem;
padding: var(--space-smaller);
margin-bottom: var(--space-one);
}
.thumb-wrap {
max-width: var(--space-jumbo);
flex-shrink: 0;
width: var(--space-medium);
display: flex;
align-items: center;
}
.image-thumb {
width: var(--space-medium);
height: var(--space-medium);
object-fit: cover;
border-radius: var(--border-radius-small);
}
.attachment-thumb {
width: var(--space-medium);
height: var(--space-medium);
font-size: var(--font-size-medium);
text-align: center;
position: relative;
top: -1px;
text-align: left;
@apply w-6 h-6 object-cover rounded-sm;
}
.file-name-wrap,
.file-size-wrap {
display: flex;
align-items: center;
padding: 0 var(--space-smaller);
@apply flex items-center py-0 px-1;
> .item {
margin: 0;
overflow: hidden;
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
@apply m-0 overflow-hidden text-xs font-medium;
}
}
.preview-header {
padding: var(--space-slab) var(--space-slab) 0 var(--space-slab);
}
.file-name-wrap {
max-width: 60%;
min-width: 50%;
overflow: hidden;
text-overflow: ellipsis;
margin-left: var(--space-small);
.item {
height: var(--space-normal);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.file-size-wrap {
width: 30%;
justify-content: center;
}
.remove-file-wrap {
display: flex;
align-items: center;
justify-content: center;
}
.remove--attachment {
width: var(--space-medium);
height: var(--space-medium);
border-radius: var(--space-medium);
font-size: var(--font-size-small);
cursor: pointer;
&:hover {
background: var(--color-background);
}
@apply w-6 h-6 rounded-md text-sm cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800;
}
</style>
@@ -16,7 +16,7 @@
</option>
</select>
<div v-if="showActionInput" class="filter__answer--wrap">
<div v-if="inputType">
<div v-if="inputType" class="w-full">
<div
v-if="inputType === 'search_select'"
class="multiselect-wrap--small"
@@ -201,102 +201,80 @@ export default {
<style lang="scss" scoped>
.filter {
background: var(--color-background);
padding: var(--space-small);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
margin-bottom: var(--space-small);
@apply bg-slate-50 dark:bg-slate-800 p-2 border border-solid border-slate-75 dark:border-slate-600 rounded-md mb-2;
&.is-a-macro {
margin-bottom: 0;
background: var(--white);
padding: var(--space-zero);
border: unset;
border-radius: unset;
@apply mb-0 bg-white dark:bg-slate-700 p-0 border-0 rounded-none;
}
}
.no-margin-bottom {
margin-bottom: 0;
@apply mb-0;
}
.filter.has-error {
background: var(--r-50);
@apply bg-red-50 dark:bg-red-800/50 border-red-100 dark:border-red-700/50;
}
.filter-inputs {
display: flex;
@apply flex;
}
.filter-error {
color: var(--r-500);
display: block;
margin: var(--space-smaller) 0;
@apply text-red-500 dark:text-red-200 block my-1 mx-0;
}
.action__question,
.filter__operator {
margin-bottom: var(--space-zero);
margin-right: var(--space-smaller);
@apply mb-0 mr-1;
}
.action__question {
max-width: 50%;
@apply max-w-[50%];
}
.action__question.full-width {
max-width: 100%;
@apply max-w-full;
}
.filter__answer--wrap {
margin-right: var(--space-smaller);
flex-grow: 1;
max-width: 50%;
@apply max-w-[50%] flex-grow mr-1 flex w-full items-center justify-start;
input {
margin-bottom: 0;
@apply mb-0;
}
}
.filter__answer {
&.answer--text-input {
margin-bottom: var(--space-zero);
@apply mb-0;
}
}
.filter__join-operator-wrap {
position: relative;
z-index: var(--z-index-twenty);
margin: var(--space-zero);
@apply relative z-20 m-0;
}
.filter__join-operator {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin: var(--space-one) var(--space-zero);
@apply flex items-center justify-center relative my-2.5 mx-0;
.operator__line {
position: absolute;
width: 100%;
border-bottom: 1px solid var(--color-border);
@apply absolute w-full border-b border-solid border-slate-75 dark:border-slate-600;
}
.operator__select {
position: relative;
width: auto;
margin-bottom: var(--space-zero) !important;
@apply relative w-auto;
}
}
.multiselect {
margin-bottom: var(--space-zero);
@apply mb-0;
}
.action-message {
margin: var(--space-small) var(--space-zero) var(--space-zero);
@apply mt-2 mx-0 mb-0;
}
// Prosemirror does not have a native way of hiding the menu bar, hence
::v-deep .ProseMirror-menubar {
display: none;
@apply hidden;
}
</style>
@@ -82,37 +82,22 @@ export default {
<style scoped>
input[type='file'] {
display: none;
@apply hidden;
}
.input-wrapper {
display: flex;
height: 39px;
background-color: var(--white);
border-radius: var(--border-radius-small);
border: 1px dashed var(--w-100);
padding: var(--space-smaller) var(--space-small);
align-items: center;
font-size: var(--font-size-mini);
cursor: pointer;
@apply flex h-9 bg-white dark:bg-slate-900 py-1 px-2 items-center text-xs cursor-pointer rounded-sm border border-dashed border-woot-100 dark:border-woot-500;
}
.success-icon {
margin-right: var(--space-small);
color: var(--g-500);
@apply text-green-500 dark:text-green-600 mr-2;
}
.error-icon {
margin-right: var(--space-small);
color: var(--r-500);
@apply text-red-500 dark:text-red-600 mr-2;
}
.processing {
cursor: not-allowed;
opacity: 0.9;
@apply cursor-not-allowed opacity-90;
}
.file-button {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
margin-bottom: 0;
@apply whitespace-nowrap overflow-hidden text-ellipsis w-full mb-0;
}
</style>
@@ -38,16 +38,18 @@ export default {
};
</script>
<style lang="scss" scoped>
<style scoped>
@tailwind components;
@layer components {
.avatar-color {
background-image: linear-gradient(to top, #c2e1ff 0%, #d6ebff 100%);
}
.dark-avatar-color {
background-image: linear-gradient(to top, #135899 0%, #135899 100%);
}
}
.avatar-container {
display: flex;
line-height: 100%;
font-weight: 500;
align-items: center;
justify-content: center;
text-align: center;
background-image: linear-gradient(to top, var(--w-100) 0%, var(--w-75) 100%);
color: var(--w-600);
cursor: default;
@apply flex leading-[100%] font-medium items-center justify-center text-center cursor-default avatar-color dark:dark-avatar-color text-woot-600 dark:text-woot-200;
}
</style>
@@ -1,5 +1,8 @@
<template>
<button class="header-section back-button" @click.capture="goBack">
<button
class="header-section flex items-center text-base font-normal mr-4 ml-2 cursor-pointer text-woot-500 dark:text-woot-500"
@click.capture="goBack"
>
<fluent-icon icon="chevron-left" />
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
</button>
@@ -61,21 +61,16 @@ export default {
}
.colorpicker--selected {
border: 1px solid var(--color-border-light);
border-radius: $space-smaller;
cursor: pointer;
height: $space-large;
margin-bottom: $space-normal;
width: $space-large;
@apply border border-solid border-slate-50 dark:border-slate-600 rounded cursor-pointer h-8 w-8 mb-4;
}
.colorpicker--chrome.vc-chrome {
@include elegant-card;
@apply shadow-lg -mt-2.5 absolute z-[9999] border border-solid border-slate-75 dark:border-slate-600 rounded;
border: 1px solid $color-border;
border-radius: $space-smaller;
margin-top: -$space-one;
position: absolute;
z-index: 9999;
::v-deep {
input {
@apply bg-white dark:bg-white;
}
}
}
</style>
@@ -1,9 +1,15 @@
<template>
<div class="row empty-state">
<h3 class="title">
<div class="empty-state py-16 px-1 ml-0 mr-0">
<h3
v-if="title"
class="text-slate-700 dark:text-slate-200 block text-center w-full text-4xl font-thin"
>
{{ title }}
</h3>
<p class="message">
<p
v-if="message"
class="block text-center text-slate-500 dark:text-slate-400 my-4 mx-auto w-[90%]"
>
{{ message }}
</p>
<slot />
@@ -14,7 +20,6 @@ export default {
props: {
title: { type: String, default: '' },
message: { type: String, default: '' },
buttonText: { type: String, default: '' },
},
};
</script>
@@ -1,11 +1,14 @@
<template>
<div class="filters">
<div class="filter" :class="{ error: v.values.$dirty && v.values.$error }">
<div class="filter-inputs">
<div>
<div
class="rounded-md p-2 border border-solid"
:class="getInputErrorClass(v.values.$dirty, v.values.$error)"
>
<div class="flex">
<select
v-if="groupedFilters"
v-model="attributeKey"
class="filter__question"
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
@change="resetFilter()"
>
<optgroup
@@ -25,7 +28,7 @@
<select
v-else
v-model="attributeKey"
class="filter__question"
class="bg-white max-w-[30%] dark:bg-slate-900 mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
@change="resetFilter()"
>
<option
@@ -38,7 +41,10 @@
</option>
</select>
<select v-model="filterOperator" class="filter__operator">
<select
v-model="filterOperator"
class="bg-white dark:bg-slate-900 max-w-[20%] mb-0 mr-1 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
>
<option
v-for="(operator, o) in operators"
:key="o"
@@ -48,7 +54,7 @@
</option>
</select>
<div v-if="showUserInput" class="filter__answer--wrap">
<div v-if="showUserInput" class="filter__answer--wrap mr-1 flex-grow">
<div
v-if="inputType === 'multi_select'"
class="multiselect-wrap--small"
@@ -90,14 +96,14 @@
v-model="values"
type="date"
:editable="false"
class="answer--text-input datepicker"
class="mb-0 datepicker"
/>
</div>
<input
v-else
v-model="values"
type="text"
class="answer--text-input"
class="mb-0"
placeholder="Enter value"
/>
</div>
@@ -113,9 +119,17 @@
</p>
</div>
<div v-if="showQueryOperator" class="filter__join-operator">
<hr class="operator__line" />
<select v-model="query_operator" class="operator__select">
<div
v-if="showQueryOperator"
class="flex items-center justify-center relative my-2.5 mx-0"
>
<hr
class="w-full absolute border-b border-solid border-slate-75 dark:border-slate-800"
/>
<select
v-model="query_operator"
class="bg-white dark:bg-slate-900 mb-0 w-auto relative text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600"
>
<option value="and">
{{ $t('FILTER.QUERY_DROPDOWN_LABELS.AND') }}
</option>
@@ -254,86 +268,26 @@ export default {
resetFilter() {
this.$emit('resetFilter');
},
getInputErrorClass(isDirty, hasError) {
return isDirty && hasError
? 'bg-red-50 dark:bg-red-800/50 border-red-100 dark:border-red-700/50'
: 'bg-slate-50 dark:bg-slate-800 border-slate-75 dark:border-slate-700/50';
},
},
};
</script>
<style lang="scss" scoped>
.filter {
background: var(--color-background);
padding: var(--space-small);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
}
.filter.error {
background: var(--r-50);
}
.filter-inputs {
display: flex;
.filter__answer--wrap {
input {
@apply bg-white dark:bg-slate-900 mb-0 text-slate-800 dark:text-slate-100 border-slate-75 dark:border-slate-600;
}
}
.filter-error {
color: var(--r-500);
display: block;
margin: var(--space-smaller) 0;
}
.filter__question,
.filter__operator {
margin-bottom: var(--space-zero);
margin-right: var(--space-smaller);
}
.filter__question {
max-width: 30%;
}
.filter__operator {
max-width: 20%;
}
.filter__answer--wrap {
margin-right: var(--space-smaller);
flex-grow: 1;
input {
margin-bottom: 0;
}
}
.filter__answer {
&.answer--text-input {
margin-bottom: var(--space-zero);
}
}
.filter__join-operator-wrap {
position: relative;
z-index: var(--z-index-twenty);
margin: var(--space-zero);
}
.filter__join-operator {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin: var(--space-one) var(--space-zero);
.operator__line {
position: absolute;
width: 100%;
border-bottom: 1px solid var(--color-border);
}
.operator__select {
position: relative;
width: auto;
margin-bottom: var(--space-zero) !important;
}
@apply text-red-500 dark:text-red-200 block my-1 mx-0;
}
.multiselect {
margin-bottom: var(--space-zero);
@apply mb-0;
}
</style>
@@ -1,13 +1,22 @@
<template>
<div class="option-item--inbox">
<span class="badge--icon">
<fluent-icon :icon="computedInboxIcon" size="14" />
<div class="flex items-center h-[2.375rem] min-w-0 py-0 px-1">
<span
class="inline-flex rounded mr-1 rtl:ml-1 rtl:mr-0 bg-slate-25 dark:bg-slate-700 p-0.5 items-center flex-shrink-0 justify-center w-6 h-6"
>
<fluent-icon
:icon="computedInboxIcon"
size="14"
class="text-slate-800 dark:text-slate-200"
/>
</span>
<div class="option__user-data">
<div class="flex flex-col w-full min-w-0 ml-1 mr-1">
<h5 class="option__title">
{{ name }}
</h5>
<p class="option__body text-truncate" :title="inboxIdentifier">
<p
class="option__body overflow-hidden whitespace-nowrap text-ellipsis"
:title="inboxIdentifier"
>
{{ inboxIdentifier || computedInboxType }}
</p>
</div>
@@ -58,45 +67,10 @@ export default {
</script>
<style lang="scss" scoped>
.option-item--inbox {
display: flex;
align-items: center;
height: 2.375rem;
min-width: 0;
padding: 0 var(--space-smaller);
}
.badge--icon {
display: inline-flex;
border-radius: var(--border-radius-small);
margin-right: var(--space-smaller);
background: var(--s-25);
padding: var(--space-micro);
align-items: center;
flex-shrink: 0;
justify-content: center;
width: var(--space-medium);
height: var(--space-medium);
}
.option__user-data {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
margin-left: var(--space-smaller);
margin-right: var(--space-smaller);
}
.option__body {
display: inline-block;
color: var(--s-600);
font-size: var(--font-size-small);
line-height: 1.3;
min-width: 0;
margin: 0;
@apply inline-block text-slate-600 dark:text-slate-200 leading-[1.3] min-w-0 m-0;
}
.option__title {
line-height: 1.1;
font-size: var(--font-size-mini);
margin: 0;
@apply leading-[1.1] text-xs m-0 text-slate-800 dark:text-slate-100;
}
</style>
@@ -1,6 +1,12 @@
<template>
<div class="inbox--name">
<fluent-icon class="inbox--icon" :icon="computedInboxClass" size="12" />
<div
class="inbox--name inline-flex items-center py-0.5 px-0 leading-3 font-medium bg-none text-slate-600 dark:text-slate-500 text-xs my-0 mx-2.5"
>
<fluent-icon
class="mr-0.5 rtl:ml-0.5 rtl:mr-0"
:icon="computedInboxClass"
size="12"
/>
{{ inbox.name }}
</div>
</template>
@@ -23,20 +29,3 @@ export default {
},
};
</script>
<style scoped>
.inbox--name {
align-items: center;
display: inline-flex;
padding: var(--space-micro) 0;
line-height: var(--space-slab);
font-weight: var(--font-weight-medium);
background: none;
color: var(--s-600);
font-size: var(--font-size-mini);
margin: 0 var(--space-one);
}
.inbox--icon {
margin-right: var(--space-micro);
}
</style>
@@ -1,9 +1,14 @@
<template>
<div class="column page-top-banner">
<h2 class="page-sub-title">
<div
class="bg-slate-25 dark:bg-slate-900 pt-4 pb-0 px-8 border-b border-solid border-slate-50 dark:border-slate-800/50"
>
<h2 class="text-2xl text-slate-800 dark:text-slate-100">
{{ headerTitle }}
</h2>
<p v-if="headerContent" class="small-12 column">
<p
v-if="headerContent"
class="w-full text-slate-600 dark:text-slate-300 text-sm mb-2"
>
{{ headerContent }}
</p>
<slot />
@@ -24,10 +29,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.page-top-banner {
border-bottom: 1px solid var(--color-border-light);
background: var(--s-25);
padding: var(--space-normal) var(--space-large) 0;
}
</style>
@@ -1,7 +1,10 @@
<template>
<footer v-if="isFooterVisible" class="footer">
<footer
v-if="isFooterVisible"
class="bg-white dark:bg-slate-800 h-[60px] border-t border-solid border-slate-75 dark:border-slate-700/50 flex items-center justify-between py-0 px-4"
>
<div class="left-aligned-wrap">
<div class="page-meta">
<div class="text-xs text-slate-600 dark:text-slate-200">
<strong>{{ firstIndex }}</strong>
- <strong>{{ lastIndex }}</strong> of
<strong>{{ totalCount }}</strong> items
@@ -161,22 +164,10 @@ export default {
</script>
<style lang="scss" scoped>
.footer {
height: 60px;
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--space-normal);
}
.page-meta {
font-size: var(--font-size-mini);
}
.goto-first,
.goto-last {
i:last-child {
margin-left: var(--space-minus-smaller);
@apply -ml-1;
}
}
</style>
@@ -128,10 +128,12 @@ export default {
return { width: statusSize, height: statusSize };
},
thumbnailClass() {
const classname = this.hasBorder ? 'border' : '';
const className = this.hasBorder
? 'border border-solid border-white dark:border-slate-700/50'
: '';
const variant =
this.variant === 'circle' ? 'thumbnail-rounded' : 'thumbnail-square';
return `user-thumbnail ${classname} ${variant}`;
return `user-thumbnail ${className} ${variant}`;
},
thumbnailBoxClass() {
const boxClass = this.variant === 'circle' ? 'is-rounded' : '';
@@ -185,14 +187,9 @@ export default {
box-sizing: border-box;
object-fit: cover;
vertical-align: initial;
&.border {
border: 1px solid white;
}
}
.source-badge {
background: white;
border-radius: var(--border-radius-small);
bottom: var(--space-minus-micro);
box-shadow: var(--shadow-small);
@@ -201,6 +198,7 @@ export default {
position: absolute;
right: 0;
width: var(--space-slab);
@apply bg-white dark:bg-slate-900;
}
.user-online-status {
@@ -213,15 +211,15 @@ export default {
}
.user-online-status--online {
background: var(--g-400);
@apply bg-green-400 dark:bg-green-400;
}
.user-online-status--busy {
background: var(--y-500);
@apply bg-yellow-500 dark:bg-yellow-500;
}
.user-online-status--offline {
background: var(--s-500);
@apply bg-slate-500 dark:bg-slate-500;
}
}
</style>
@@ -1,12 +1,15 @@
<template>
<div class="row--user-block">
<div class="flex items-center text-left">
<thumbnail
:src="user.thumbnail"
:size="size"
:username="user.name"
:status="user.availability_status"
/>
<h6 class="text-block-title user-name text-truncate text-capitalize">
<h6
class="my-0 mx-2 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
:class="textClass"
>
{{ user.name }}
</h6>
</div>
@@ -27,18 +30,10 @@ export default {
type: String,
default: '20px',
},
textClass: {
type: String,
default: 'text-xs text-slate-600',
},
},
};
</script>
<style scoped lang="scss">
.row--user-block {
align-items: center;
display: flex;
text-align: left;
.user-name {
margin: 0 var(--space-small);
}
}
</style>
@@ -445,69 +445,87 @@ export default {
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss';
.ProseMirror-menubar-wrapper {
display: flex;
flex-direction: column;
@apply flex flex-col;
.ProseMirror-menubar {
min-height: var(--space-two) !important;
margin-left: var(--space-minus-one);
padding-bottom: 0;
}
@apply -ml-2.5 pb-0 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100;
.ProseMirror-menu-active {
@apply bg-slate-75 dark:bg-slate-800;
}
}
> .ProseMirror {
padding: 0;
word-break: break-word;
@apply p-0 break-words text-slate-800 dark:text-slate-100;
h1,
h2,
h3,
h4,
h5,
h6,
p {
@apply text-slate-800 dark:text-slate-100;
}
blockquote {
@apply border-slate-400 dark:border-slate-500;
p {
@apply text-slate-600 dark:text-slate-400;
}
}
ol li {
@apply list-item list-decimal;
}
}
}
.editor-root {
width: 100%;
position: relative;
@apply w-full relative;
}
.ProseMirror-woot-style {
min-height: 5rem;
max-height: 7.5rem;
overflow: auto;
@apply overflow-auto min-h-[5rem] max-h-[7.5rem];
}
.ProseMirror-prompt {
z-index: var(--z-index-highest);
background: var(--color-background-light);
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
@apply z-50 bg-slate-25 dark:bg-slate-700 rounded-md border border-solid border-slate-75 dark:border-slate-800;
}
.is-private {
.prosemirror-mention-node {
font-weight: var(--font-weight-medium);
background: var(--s-50);
color: var(--s-900);
padding: 0 var(--space-smaller);
@apply font-medium bg-yellow-100 dark:bg-yellow-800 text-slate-900 dark:text-slate-25 py-0 px-1;
}
.ProseMirror-menubar {
background: var(--y-50);
.ProseMirror-menubar-wrapper {
.ProseMirror-menubar {
@apply bg-yellow-100 dark:bg-yellow-800 text-slate-700 dark:text-slate-25;
}
> .ProseMirror {
@apply text-slate-800 dark:text-slate-25;
p {
@apply text-slate-800 dark:text-slate-25;
}
}
}
}
.editor-wrap {
margin-bottom: var(--space-normal);
@apply mb-4;
}
.message-editor {
border: 1px solid var(--color-border);
border-radius: var(--border-radius-normal);
padding: 0 var(--space-slab);
margin-bottom: 0;
@apply border border-solid border-slate-200 dark:border-slate-600 bg-white dark:bg-slate-900 rounded-md py-0 px-1 mb-0;
}
.editor_warning {
border: 1px solid var(--r-400);
@apply border border-solid border-red-400 dark:border-red-400;
}
.editor-warning__message {
color: var(--r-400);
font-weight: var(--font-weight-normal);
padding: var(--space-smaller) 0 0 0;
@apply text-red-400 dark:text-red-400 font-normal pt-1 pb-0 px-0;
}
</style>
@@ -103,7 +103,7 @@
class="modal-mask"
>
<fluent-icon icon="cloud-backup" />
<h4 class="page-sub-title">
<h4 class="page-sub-title text-slate-600 dark:text-slate-200">
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
</h4>
</div>
@@ -323,48 +323,35 @@ export default {
<style lang="scss" scoped>
.bottom-box {
display: flex;
justify-content: space-between;
padding: var(--space-slab) var(--space-normal);
@apply flex justify-between py-3 px-4;
&.is-note-mode {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
.left-wrap .button {
margin-right: var(--space-small);
}
.left-wrap {
align-items: center;
display: flex;
@apply items-center flex gap-2;
}
.right-wrap {
display: flex;
@apply flex;
}
::v-deep .file-uploads {
label {
cursor: pointer;
@apply cursor-pointer;
}
&:hover .button {
background: var(--s-100);
&:hover button {
@apply dark:bg-slate-800 bg-slate-100;
}
}
.modal-mask {
color: var(--s-600);
background: var(--white-transparent);
flex-direction: column;
}
.page-sub-title {
color: var(--s-600);
@apply text-slate-600 dark:text-slate-200 bg-white_transparent dark:bg-black_transparent flex-col;
}
.icon {
font-size: 5rem;
@apply text-[5rem];
}
</style>
@@ -1,6 +1,6 @@
<template>
<div class="top-box">
<div class="mode-wrap button-group">
<div class="bg-black-50 flex justify-between dark:bg-slate-800">
<div class="button-group">
<woot-button
variant="clear"
class="button--reply"
@@ -20,8 +20,8 @@
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
</woot-button>
</div>
<div class="action-wrap">
<div v-if="isMessageLengthReachingThreshold" class="tabs-title">
<div class="flex items-center my-0 mx-4">
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
<span :class="charLengthClass">
{{ characterLengthWarning }}
</span>
@@ -90,7 +90,7 @@ export default {
};
},
charLengthClass() {
return this.charactersRemaining < 0 ? 'message-error' : 'message-length';
return this.charactersRemaining < 0 ? 'text-red-600' : 'text-slate-600';
},
characterLengthWarning() {
return this.charactersRemaining < 0
@@ -118,70 +118,34 @@ export default {
</script>
<style lang="scss" scoped>
.top-box {
display: flex;
justify-content: space-between;
@apply bg-black-50 dark:bg-slate-800;
}
.button-group {
border: 0;
padding: 0;
margin: 0;
@apply flex border-0 p-0 m-0;
.button {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
padding: var(--space-one) var(--space-normal);
margin: 0;
position: relative;
z-index: 1;
@apply text-sm font-medium py-2.5 px-4 m-0 relative z-10;
&.is-active {
background: white;
@apply bg-white dark:bg-slate-900;
}
}
.button--reply {
border-radius: 0;
@apply border-r border-b-0 border-l-0 border-slate-50 dark:border-slate-700;
@apply border-r rounded-none border-b-0 border-l-0 border-t-0 border-slate-50 dark:border-slate-700;
&:hover,
&:focus {
@apply border-r border-slate-50 dark:border-slate-700;
}
}
.button--note {
border-radius: 0;
@apply border-l-0 rounded-none;
&.is-active {
@apply border-r border-b-0 border-slate-50 dark:border-slate-700;
background: var(--y-50);
@apply border-r border-b-0 bg-yellow-100 dark:bg-yellow-800 border-t-0 border-slate-50 dark:border-slate-700;
}
&:hover,
&:active {
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-700;
}
}
}
.button--note {
color: var(--y-600);
}
.action-wrap {
display: flex;
align-items: center;
margin: 0 var(--space-normal);
font-size: var(--font-size-mini);
.message-error {
color: var(--r-600);
}
.message-length {
color: var(--s-600);
}
@apply text-yellow-600 dark:text-yellow-600;
}
</style>
@@ -1,57 +1 @@
<template>
<select v-model="activeStatus" class="status--filter" @change="onTabChange()">
<option
v-for="(value, status) in $t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS')"
:key="status"
:value="status"
>
{{ value['TEXT'] }}
</option>
</select>
</template>
<script>
import wootConstants from 'dashboard/constants/globals';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { hasPressedAltAndBKey } from 'shared/helpers/KeyboardHelpers';
export default {
mixins: [eventListenerMixins],
data: () => ({
activeStatus: wootConstants.STATUS_TYPE.OPEN,
}),
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndBKey(e)) {
if (this.activeStatus === wootConstants.STATUS_TYPE.OPEN) {
this.activeStatus = wootConstants.STATUS_TYPE.RESOLVED;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.RESOLVED) {
this.activeStatus = wootConstants.STATUS_TYPE.PENDING;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.PENDING) {
this.activeStatus = wootConstants.STATUS_TYPE.SNOOZED;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.SNOOZED) {
this.activeStatus = wootConstants.STATUS_TYPE.ALL;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.ALL) {
this.activeStatus = wootConstants.STATUS_TYPE.OPEN;
}
}
this.onTabChange();
},
onTabChange() {
this.$store.dispatch('setChatStatusFilter', this.activeStatus);
this.$emit('onChangeFilter', this.activeStatus);
},
},
};
</script>
<style lang="scss" scoped>
.status--filter {
background-color: var(--color-background-light);
border: 1px solid var(--color-border);
font-size: var(--font-size-mini);
height: var(--space-medium);
margin: 0 var(--space-smaller);
padding: 0 var(--space-medium) 0 var(--space-small);
width: auto;
}
</style>
<!-- // not using this component -->
@@ -1,22 +1,30 @@
<template>
<div class="column">
<div>
<woot-modal-header :header-title="filterModalHeaderTitle">
<p>{{ filterModalSubTitle }}</p>
<p class="text-slate-600 dark:text-slate-200">
{{ filterModalSubTitle }}
</p>
</woot-modal-header>
<div class="column modal-content">
<div v-if="isFolderView" class="columns">
<div class="p-8">
<div v-if="isFolderView">
<label class="input-label" :class="{ error: !activeFolderNewName }">
{{ $t('FILTER.FOLDER_LABEL') }}
<input v-model="activeFolderNewName" type="text" class="name-input" />
<input
v-model="activeFolderNewName"
type="text"
class="folder-input border-slate-75 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100"
/>
<span v-if="!activeFolderNewName" class="message">
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
</span>
</label>
<label class="input-label">
<label class="mb-1">
{{ $t('FILTER.FOLDER_QUERY_LABEL') }}
</label>
</div>
<div class="medium-12 columns filters-wrap">
<div
class="p-4 rounded-lg bg-slate-25 dark:bg-slate-900 border border-solid border-slate-50 dark:border-slate-700/50 mb-4"
>
<filter-input-box
v-for="(filter, i) in appliedFilters"
:key="i"
@@ -37,7 +45,7 @@
@resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)"
/>
<div class="filter-actions">
<div class="mt-4">
<woot-button
icon="add"
color-scheme="success"
@@ -49,8 +57,8 @@
</woot-button>
</div>
</div>
<div class="medium-12 columns">
<div class="modal-footer justify-content-end w-full">
<div class="w-full">
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-button class="button clear" @click.prevent="onClose">
{{ $t('FILTER.CANCEL_BUTTON_LABEL') }}
</woot-button>
@@ -371,23 +379,7 @@ export default {
};
</script>
<style lang="scss" scoped>
.filters-wrap {
padding: var(--space-normal);
border-radius: var(--border-radius-large);
border: 1px solid var(--color-border);
background: var(--color-background-light);
margin-bottom: var(--space-normal);
}
.filter-actions {
margin-top: var(--space-normal);
}
.input-label {
margin-bottom: var(--space-smaller);
.name-input {
width: 50%;
}
.folder-input {
@apply w-[50%];
}
</style>
@@ -1,5 +1,5 @@
<template>
<div class="position-relative">
<div class="relative flex">
<woot-button
v-tooltip.right="$t('CHAT_LIST.SORT_TOOLTIP_LABEL')"
variant="smooth"
@@ -12,10 +12,12 @@
<div
v-if="showActionsDropdown"
v-on-clickaway="closeDropdown"
class="dropdown-pane dropdown-pane--open basic-filter"
class="dropdown-pane dropdown-pane--open mt-1 right-0 basic-filter"
>
<div class="filter__item">
<span>{{ this.$t('CHAT_LIST.CHAT_SORT.STATUS') }}</span>
<div class="items-center flex justify-between last:mt-4">
<span class="text-slate-800 dark:text-slate-100 text-xs font-medium">{{
this.$t('CHAT_LIST.CHAT_SORT.STATUS')
}}</span>
<filter-item
type="status"
:selected-value="chatStatus"
@@ -24,8 +26,10 @@
@onChangeFilter="onChangeFilter"
/>
</div>
<div class="filter__item">
<span>{{ this.$t('CHAT_LIST.CHAT_SORT.ORDER_BY') }}</span>
<div class="items-center flex justify-between last:mt-4">
<span class="text-slate-800 dark:text-slate-100 text-xs font-medium">{{
this.$t('CHAT_LIST.CHAT_SORT.ORDER_BY')
}}</span>
<filter-item
type="sort"
:selected-value="chatSortFilter"
@@ -87,36 +91,6 @@ export default {
</script>
<style lang="scss" scoped>
.basic-filter {
margin-top: var(--space-smaller);
padding: var(--space-normal);
right: 0;
width: 13.125rem;
span {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
}
.filter__item {
align-items: center;
display: flex;
justify-content: space-between;
&:last-child {
margin-top: var(--space-normal);
}
span {
font-size: var(--font-size-mini);
}
}
}
.icon {
margin-right: var(--space-smaller);
}
.dropdown-icon {
margin-left: var(--space-smaller);
@apply w-52 p-4 top-6;
}
</style>
@@ -13,7 +13,7 @@
<woot-tabs
v-if="dashboardApps.length && currentChat.id"
:index="activeIndex"
class="dashboard-app--tabs"
class="dashboard-app--tabs bg-white dark:bg-slate-900 -mt-px"
@change="onDashboardAppTabChange"
>
<woot-tabs-item
@@ -34,7 +34,10 @@
@contact-panel-toggle="onToggleContactPanel"
/>
<empty-state v-else :is-on-expanded-layout="isOnExpandedLayout" />
<div v-show="showContactPanel" class="conversation-sidebar-wrap">
<div
v-show="showContactPanel"
class="conversation-sidebar-wrap basis-full sm:basis-[17.5rem] md:basis-[18.75rem] lg:basis-[19.375rem] xl:basis-[20.625rem] 2xl:basis-[25rem] rtl:border-r border-slate-50 dark:border-slate-700 h-auto overflow-auto z-10 flex-shrink-0 flex-grow-0"
>
<contact-panel
v-if="showContactPanel"
:conversation-id="currentChat.id"
@@ -58,7 +61,7 @@ import { mapGetters } from 'vuex';
import ContactPanel from 'dashboard/routes/dashboard/conversation/ContactPanel';
import ConversationHeader from './ConversationHeader';
import DashboardAppFrame from '../DashboardApp/Frame.vue';
import EmptyState from './EmptyState';
import EmptyState from './EmptyState/EmptyState';
import MessagesView from './MessagesView';
export default {
@@ -141,12 +144,8 @@ export default {
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.conversation-details-wrap {
display: flex;
flex-direction: column;
min-width: 0;
width: 100%;
@apply flex flex-col min-w-0 w-full;
&.with-border-right {
@apply border-r border-slate-50 dark:border-slate-700;
@@ -154,40 +153,18 @@ export default {
}
.dashboard-app--tabs {
background: var(--white);
margin-top: -1px;
min-height: var(--dashboard-app-tabs-height);
::v-deep {
.tabs-title {
a {
@apply pb-2 pt-1;
}
}
}
}
.conversation-sidebar-wrap {
@apply border-r border-slate-50 dark:border-slate-700;
background: white;
flex-basis: 100%;
@include breakpoint(medium up) {
flex-basis: 17.5rem;
}
@include breakpoint(large up) {
flex-basis: 18.75rem;
}
@include breakpoint(xlarge up) {
flex-basis: 19.375rem;
}
@include breakpoint(xxlarge up) {
flex-basis: 20.625rem;
}
@include breakpoint(xxxlarge up) {
flex-basis: 25rem;
}
&::v-deep .contact--panel {
width: 100%;
height: 100%;
max-width: 100%;
@apply w-full h-full max-w-full;
}
}
</style>
@@ -1,8 +1,8 @@
<template>
<div
class="conversation group border-t border-transparent"
class="conversation flex flex-shrink-0 flex-grow-0 w-auto max-w-full cursor-pointer relative py-0 px-4 border-transparent border-l-2 border-t-0 border-b-0 border-r-0 border-solid items-start hover:bg-slate-25 dark:hover:bg-slate-800 group"
:class="{
active: isActiveChat,
'active bg-slate-25 dark:bg-slate-800 border-woot-500': isActiveChat,
'unread-chat': hasUnread,
'has-inbox-name': showInboxName,
'conversation-selected': selected,
@@ -31,41 +31,50 @@
size="40px"
/>
<div
class="conversation--details border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800 columns"
class="py-3 px-0 border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800/75 columns"
>
<div class="conversation--metadata">
<div class="flex justify-between">
<inbox-name v-if="showInboxName" :inbox="inbox" />
<div class="conversation-metadata-attributes">
<div class="flex gap-2 ml-2 rtl:mr-2 rtl:ml-0">
<span
v-if="showAssignee && assignee.name"
class="label assignee-label text-truncate"
class="text-slate-500 dark:text-slate-400 text-xs font-medium leading-3 py-0.5 px-0 inline-flex text-ellipsis overflow-hidden whitespace-nowrap"
>
<fluent-icon icon="person" size="12" />
<fluent-icon
icon="person"
size="12"
class="text-slate-500 dark:text-slate-400"
/>
{{ assignee.name }}
</span>
<priority-mark :priority="chat.priority" />
</div>
</div>
<h4 class="conversation--user">
<h4
class="conversation--user text-sm my-0 mx-2 capitalize pt-0.5 text-ellipsis overflow-hidden whitespace-nowrap w-[60%] text-slate-900 dark:text-slate-100"
>
{{ currentContact.name }}
</h4>
<p v-if="lastMessageInChat" class="conversation--message">
<p
v-if="lastMessageInChat"
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
>
<fluent-icon
v-if="isMessagePrivate"
size="16"
class="message--attachment-icon last-message-icon"
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
icon="lock-closed"
/>
<fluent-icon
v-else-if="messageByAgent"
size="16"
class="message--attachment-icon last-message-icon"
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
icon="arrow-reply"
/>
<fluent-icon
v-else-if="isMessageAnActivity"
size="16"
class="message--attachment-icon last-message-icon"
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
icon="info"
/>
<span v-if="lastMessageInChat.content">
@@ -75,7 +84,7 @@
<fluent-icon
v-if="attachmentIcon"
size="16"
class="message--attachment-icon"
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
:icon="attachmentIcon"
/>
{{ this.$t(`${attachmentMessageContent}`) }}
@@ -84,20 +93,31 @@
{{ $t('CHAT_LIST.NO_CONTENT') }}
</span>
</p>
<p v-else class="conversation--message">
<fluent-icon size="16" class="message--attachment-icon" icon="info" />
<p
v-else
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
>
<fluent-icon
size="16"
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
icon="info"
/>
<span>
{{ this.$t(`CHAT_LIST.NO_MESSAGES`) }}
</span>
</p>
<div class="conversation--meta">
<span class="timestamp">
<div class="conversation--meta flex flex-col absolute right-4 top-4">
<span class="text-black-600 text-xxs font-normal leading-4 ml-auto">
<time-ago
:last-activity-timestamp="chat.timestamp"
:created-at-timestamp="chat.created_at"
/>
</span>
<span class="unread">{{ unreadCount > 9 ? '9+' : unreadCount }}</span>
<span
class="unread shadow-lg rounded-full hidden text-xxs font-semibold h-4 leading-4 ml-auto mt-1 min-w-[1rem] px-1 py-0 text-center text-white bg-green-400"
>
{{ unreadCount > 9 ? '9+' : unreadCount }}
</span>
</div>
<card-labels :conversation-id="chat.id" />
</div>
@@ -400,94 +420,51 @@ export default {
</script>
<style lang="scss" scoped>
.conversation {
align-items: flex-start;
&.unread-chat {
.unread {
@apply block;
}
.conversation--message {
@apply font-semibold;
}
.conversation--user {
@apply font-semibold;
}
}
&:hover {
background: var(--color-background-light);
&.compact {
@apply pl-0;
.conversation--details {
@apply rounded-sm ml-0 pl-5 pr-2;
}
}
&::v-deep .user-thumbnail-box {
margin-top: var(--space-normal);
@apply mt-4;
}
}
.conversation-selected {
background: var(--color-background-light);
}
.has-inbox-name {
&::v-deep .user-thumbnail-box {
margin-top: var(--space-large);
&.conversation-selected {
@apply bg-slate-25 dark:bg-slate-800;
}
&.has-inbox-name {
&::v-deep .user-thumbnail-box {
@apply mt-8;
}
.checkbox-wrapper {
@apply mt-8;
}
.conversation--meta {
@apply mt-4;
}
}
.checkbox-wrapper {
margin-top: var(--space-large);
}
.conversation--meta {
margin-top: var(--space-normal);
}
}
@apply h-10 w-10 flex items-center justify-center rounded-full cursor-pointer mt-4 hover:bg-woot-100 dark:hover:bg-woot-800;
.conversation--details {
.conversation--user {
padding-top: var(--space-micro);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 60%;
}
}
.last-message-icon {
color: var(--s-600);
}
.conversation--metadata {
display: flex;
justify-content: space-between;
.label {
background: none;
color: var(--s-500);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
line-height: var(--space-slab);
padding: var(--space-micro) 0 var(--space-micro) 0;
}
.conversation-metadata-attributes {
display: flex;
gap: var(--space-small);
margin-left: var(--space-small);
}
.assignee-label {
display: inline-flex;
max-width: 50%;
}
}
.message--attachment-icon {
margin-top: var(--space-minus-micro);
vertical-align: middle;
}
.checkbox-wrapper {
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
margin-top: var(--space-normal);
cursor: pointer;
&:hover {
background-color: var(--w-100);
}
input[type='checkbox'] {
margin: var(--space-zero);
cursor: pointer;
input[type='checkbox'] {
@apply m-0 cursor-pointer;
}
}
}
</style>
@@ -1,41 +1,56 @@
<template>
<div class="conv-header flex-col md:flex-row">
<div
class="bg-white dark:bg-slate-900 flex justify-between items-center py-2 px-4 border-b border-slate-50 dark:border-slate-800/50 flex-col md:flex-row"
>
<div
class="flex-1 w-100 flex flex-col md:flex-row items-center justify-center"
>
<div class="user">
<back-button v-if="showBackButton" :back-url="backButtonUrl" />
<div
class="flex justify-center items-center mr-4 rtl:mr-0 rtl:ml-4 min-w-0"
>
<back-button
v-if="showBackButton"
:back-url="backButtonUrl"
class="ltr:ml-0 rtl:mr-0 rtl:ml-4"
/>
<Thumbnail
:src="currentContact.thumbnail"
:badge="inboxBadge"
:username="currentContact.name"
:status="currentContact.availability_status"
/>
<div class="user--profile__meta">
<div class="items-start flex flex-col ml-2 rtl:ml-0 rtl:mr-2 min-w-0">
<woot-button
variant="link"
color-scheme="secondary"
class="text-truncate"
class="overflow-hidden whitespace-nowrap text-ellipsis"
@click.prevent="$emit('contact-panel-toggle')"
>
<h3 class="sub-block-title user--name text-truncate">
<h3
class="text-base inline-block leading-tight capitalize m-0 p-0 text-ellipsis overflow-hidden whitespace-nowrap text-slate-900 dark:text-slate-100"
>
<span>{{ currentContact.name }}</span>
<fluent-icon
v-if="!isHMACVerified"
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
size="14"
class="hmac-warning__icon"
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0.5"
icon="warning"
/>
</h3>
</woot-button>
<div class="conversation--header--actions text-truncate">
<div
class="conversation--header--actions items-center flex text-xs gap-2 text-ellipsis overflow-hidden whitespace-nowrap"
>
<inbox-name v-if="hasMultipleInboxes" :inbox="inbox" />
<span v-if="isSnoozed" class="snoozed--display-text">
<span
v-if="isSnoozed"
class="font-medium text-yellow-600 dark:text-yellow-500"
>
{{ snoozedDisplayText }}
</span>
<woot-button
class="user--profile__button"
class="p-0"
size="small"
variant="link"
@click="$emit('contact-panel-toggle')"
@@ -46,8 +61,8 @@
</div>
</div>
<div
class="header-actions-wrap mt-3 lg:mt-0"
:class="{ 'has-open-sidebar': isContactPanelOpen }"
class="header-actions-wrap items-center flex flex-row flex-grow justify-end mt-3 lg:mt-0"
:class="{ 'justify-end': isContactPanelOpen }"
>
<more-actions :conversation-id="currentChat.id" />
</div>
@@ -161,54 +176,9 @@ export default {
</script>
<style lang="scss" scoped>
.conv-header {
flex: 0 0 var(--space-jumbo);
flex-direction: row;
}
.option__desc {
display: flex;
align-items: center;
}
.option__desc {
&::v-deep .status-badge {
margin-right: var(--space-small);
min-width: 0;
flex-shrink: 0;
}
}
.user--name {
display: inline-block;
line-height: 1.2;
text-transform: capitalize;
margin: 0;
padding: 0;
}
.conversation--header--actions {
align-items: center;
display: flex;
font-size: var(--font-size-mini);
gap: var(--space-small);
::v-deep .inbox--name {
margin: 0;
@apply m-0;
}
.user--profile__button {
padding: 0;
}
.snoozed--display-text {
font-weight: var(--font-weight-medium);
color: var(--y-600);
}
}
.hmac-warning__icon {
color: var(--y-600);
margin: 0 var(--space-micro);
}
</style>
@@ -1,12 +1,12 @@
<template>
<woot-modal :show.sync="show" :on-close="onCancel">
<div class="column content-box">
<div class="h-auto overflow-auto flex flex-col">
<woot-modal-header
:header-title="$t('EMAIL_TRANSCRIPT.TITLE')"
:header-content="$t('EMAIL_TRANSCRIPT.DESC')"
/>
<form @submit.prevent="onSubmit">
<div class="medium-12 columns">
<form class="w-full" @submit.prevent="onSubmit">
<div class="w-full">
<div v-if="currentChat.meta.sender && currentChat.meta.sender.email">
<input
id="contact"
@@ -43,7 +43,7 @@
$t('EMAIL_TRANSCRIPT.FORM.SEND_TO_OTHER_EMAIL_ADDRESS')
}}</label>
</div>
<div v-if="sentToOtherEmailAddress" class="medium-6 columns">
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
<label :class="{ error: $v.email.$error }">
<input
v-model.trim="email"
@@ -57,16 +57,14 @@
</label>
</div>
</div>
<div class="modal-footer">
<div class="medium-12 row">
<woot-submit-button
:button-text="$t('EMAIL_TRANSCRIPT.SUBMIT')"
:disabled="!isFormValid"
/>
<button class="button clear" @click.prevent="onCancel">
{{ $t('EMAIL_TRANSCRIPT.CANCEL') }}
</button>
</div>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-submit-button
:button-text="$t('EMAIL_TRANSCRIPT.SUBMIT')"
:disabled="!isFormValid"
/>
<button class="button clear" @click.prevent="onCancel">
{{ $t('EMAIL_TRANSCRIPT.CANCEL') }}
</button>
</div>
</form>
</div>

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