Compare commits

..
Author SHA1 Message Date
Shivam Mishra 79a76bc9df refactor: use MutexApplicationJob 2023-08-23 11:59:24 +07:00
Shivam Mishra 77b1804eef feat: create mutex application job 2023-08-23 11:59:06 +07:00
Shivam Mishra a962308a23 feat: add mutex for slack send job 2023-08-23 11:59:06 +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
2b4f4f0b5c [Snyk] Security upgrade administrate from 0.18.0 to 0.19.0 (#7547)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-07-20 12:47:54 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d66eb8b21f chore(deps): bump word-wrap from 1.2.3 to 1.2.4 (#7543)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 12:25:00 -07:00
Pranav Raj SandGitHub 27419eef66 feat: Add report on customer waiting time (#7545) 2023-07-20 12:01:22 -07:00
Pranav Raj SandGitHub d7566c453d chore: Take the count directly rather than grouping the conversations (#7535) 2023-07-19 12:12:30 -07:00
Pranav Raj SandGitHub 25ed66edf5 feat: Update reports UI to make it better (#7544) 2023-07-19 12:12:15 -07:00
f72be94323 chore: [Snyk] Fix for 1 vulnerabilities (#7466)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-07-19 21:41:55 +03:00
Vishnu NarayananandGitHub ea825d49da feat: add auditlogs for account model (#7511) 2023-07-19 23:52:34 +05:30
Sojan JoseandGitHub 1d718b92b7 chore: Fix schema disparities (#7554)
- Fixing the schema disparities that crept up during merges
- Also, fix the issue with migrate command regenerating the schema for contacts.rb model
2023-07-19 19:15:43 +03:00
Tejaswini ChileandGitHub f3f665cabb fix: Sentiment analysis queue update (#7552) 2023-07-19 20:27:20 +05:30
Vishnu NarayananandGitHub 8babf7dec3 feat: add debug gem and more make commands (#7509) 2023-07-18 13:22:02 +05:30
Tejaswini ChileandGitHub 5b480f563d feat: Sentiment model download and upload to vendor (#7526) 2023-07-18 12:17:50 +05:30
Pranav Raj SandGitHub 3a77e672f8 feat: Compute average response time of replies (#7530) 2023-07-17 11:21:31 -07:00
Shivam MishraandGitHub 3e75ac2de5 feat: update the UX around label suggestions (#7532) 2023-07-17 15:10:56 +05:30
91c1061214 feat: Add more AI options (#7502)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-16 12:25:16 -07:00
ec65b43993 feat: Add support for API key authentication in Twilio (#7523)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-07-14 19:20:54 -07:00
Shivam MishraandGitHub 1a81245987 feat: Update label suggestion visibility (#7525) 2023-07-14 19:20:03 -07:00
Pranav Raj SandGitHub 2c3ad73542 fix: Update the case for validation where SSO link is used (#7527) 2023-07-14 08:30:08 -07:00
Shivam MishraandGitHub 59b1d59574 fix: idb is not available in firefox private mode [CW-2217] (#7524) 2023-07-14 13:35:30 +05:30
Pranav Raj SandGitHub 9de2edd300 fix: Cannot read properties of undefined (reading 'filter') (#7522) 2023-07-13 15:56:28 -07:00
Pranav Raj SandGitHub 66751b71dd fix: Fix issue null is not iterable (#7521) 2023-07-13 15:47:33 -07:00
Pranav Raj SandGitHub 7e415fbee3 fix: Disable predictions if model is absent (#7520) 2023-07-13 14:40:16 -07:00
Muhsin KelothandGitHub 50a927bac2 feat: Add reply editor mode to the store (#7515) 2023-07-13 14:56:53 +05:30
Muhsin KelothandGitHub 19ff738211 feat: Add more options for AI reply suggestions (#7493) 2023-07-13 10:26:25 +05:30
Shivam MishraandGitHub 7c080fa9fa feat: label suggestion UI (#7480) 2023-07-13 09:16:09 +05:30
91e2da5e74 fix: Uncategorized article display error - HelpCenter-> Portal -> Uncategorised Article (#7504)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-12 16:34:16 -07:00
Pranav Raj SandGitHub 21691f2407 fix: Update breaking specs on sentiment and open AI integration (#7513) 2023-07-12 14:43:11 -07:00
d495411ef5 feat: Minor improvements to login UI (#7494)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-12 13:35:02 -07:00
918e1f25c2 feat: Improve summary prompt (#7510)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-12 13:09:19 -07:00
Tejaswini ChileandGitHub 10dd0ba647 feat: Sentiment Analysis (#7475) 2023-07-12 15:03:31 +05:30
Pranav Raj SandGitHub 550bea099c fix: Add appearance-none to input field for ring to work (#7507) 2023-07-11 19:07:02 -07:00
Nithin David ThomasandGitHub c7bf93ef8a fix: Adds a space after mention to improve UX (#7506) 2023-07-11 17:37:37 -07:00
Shivam MishraandGitHub ec7434d000 feat: Remove twitter (#7505) 2023-07-11 14:00:11 -07:00
Muhsin KelothandGitHub 941e7e06cf feat: Add draft messages store (#7476) 2023-07-11 18:29:42 +05:30
Pranav Raj SandGitHub 09f46aa912 chore: Update onMessage event to include conversation id (#7497) 2023-07-10 15:04:31 -07:00
Vishnu NarayananandGitHub 332ab5888c feat: Disable fb/instagram if env variables are not configured (#7498) 2023-07-10 12:28:55 -07:00
Shivam MishraandGitHub a940f4c1d2 feat: Allow label suggestions with OpenAI APIs (#7428) 2023-07-10 14:40:54 +05:30
Muhsin KelothandGitHub 5b4f92ccd2 fix: Add the option to revert the AI generated content (#7458) 2023-07-10 11:30:22 +05:30
Pranav Raj SandGitHub ad03be4529 fix: Update the index files to fix missing translations (#7484) 2023-07-07 20:11:34 -07:00
Sivin VargheseandGitHub 571e6bd0ec fix: Update broken design elements in dashboard (#7468) 2023-07-06 11:58:54 -07:00
528da1d2cf feat: Add support for dark mode in more pages
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-05 19:26:24 -07:00
Pranav Raj SandGitHub 4e8d17f017 chore: Update specs and warnings in console (#7467) 2023-07-05 18:32:55 -07:00
3054a4cb59 feat: Add support for dark mode in dashboard (#7460)
- Add config for TailwindCSS
- Enable HMR
- Add a config in LocalStorage for Dark Mode

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-07-05 12:13:32 -07:00
Tejaswini ChileandGitHub 71837bedf9 feat: Ability to customise the email sender name [CW-1629] (#7345) 2023-07-04 20:46:01 +05:30
3c4514c9f7 chore: Update translations (#7415)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-07-04 14:23:56 +05:30
Nithin David ThomasandGitHub 4c7c1cd78a chore: Change order of resolve action dropdown buttons (#7456) 2023-07-03 19:39:32 -07:00
985b3f3bfc chore: Reset the base font-size to 16px instead of 10px (#7455)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2023-07-04 03:23:00 +05:30
Clairton Rodrigo HeinzenandGitHub 986abde531 fix: Cannot use 'in' operator to search for 'last_activity_at' (#7447) 2023-07-03 14:12:47 -07:00
b57063a8b8 feat: Support dark mode in login pages (#7420)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-07-01 07:49:52 +05:30
Pranav Raj SandGitHub 022f4f899f Revert "feat: Support Azure single-tenant application using the Graph… (#7436) 2023-06-29 16:50:18 -07:00
Sivin VargheseandGitHub 191b8a64fe chore: Remove URLs from translations (#7414) 2023-06-29 15:09:02 +05:30
ea78020f2f fix: Translation issues (#7416)
Co-authored-by: Hricha Shandily <103104754+Hricha-Shandily@users.noreply.github.com>
2023-06-29 14:41:23 +05:30
Muhsin KelothandGitHub dc1a41b136 fix: Set date hours to 9 in snooze time (#7426) 2023-06-29 14:38:03 +05:30
Shivam MishraandGitHub 28e7a5d228 fix: CC conditions when the last email is from someone else (#7010) 2023-06-28 20:28:44 -07:00
40830046e8 feat: Audit Logs for Account User Changes (#7405)
- Audit log for user invitations: https://linear.app/chatwoot/issue/CW-1768/invited-a-user-to-the-account
- Audit log for change role: https://linear.app/chatwoot/issue/CW-1767/name-or-email-changed-the-role-of-the-user-email-to-agent-or-admin
- Audit log for status change: https://linear.app/chatwoot/issue/CW-1766/availability-status-as-events-for-audit-logs


Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2023-06-28 22:42:06 +05:30
Tejaswini ChileandGitHub d05c953eef feat: Support Azure single-tenant application using the Graph API (#6728) (#6878) 2023-06-28 08:13:08 +05:30
4a770fdea7 feat: Add Plus Jakarta Font to dashboard (#7421)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-06-27 17:39:37 -07:00
Tejaswini ChileandGitHub 239b1e2bf1 feat: Include email in the searched result (#7397) 2023-06-27 21:05:36 +05:30
f963e00731 fix: capture user and ip details on Inbox delete (#7395)
Fixes: https://linear.app/chatwoot/issue/CW-1772/ip-address-and-user-details-are-missing-in-some-of-the-logs

Co-authored-by: Sojan <sojan@pepalo.com>
2023-06-27 19:49:24 +05:30
TOMMYandGitHub 155a5b9947 fix: Remove free space (#7411) 2023-06-27 19:28:38 +05:30
TOMMYandGitHub 26a75b250d Merge pull request from GHSA-2472-ggjh-43h2 2023-06-27 17:22:54 +05:30
Pranav Raj SandGitHub 20b84ee6a1 chore: Fix darkMode styles in widget (#7408) 2023-06-26 14:43:22 -07:00
Pranav Raj SandGitHub f1f14312fe chore: Upgrade Tailwind CSS to 3.3.2 (#7380) 2023-06-26 11:27:16 -07:00
Tejaswini ChileandGitHub 212d7caab1 fix: log User is authenticated but not connected (#7394) 2023-06-26 16:07:40 +05:30
9cda16d0e8 fix: Remove overflow string in label delete confirm button (#7386)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2023-06-26 13:26:47 +05:30
Sivin VargheseandGitHub a8acbb7427 fix: Issue with i18n in update banner (#7392) 2023-06-26 11:32:40 +05:30
1176e5eb8a feat: Custom phone input in pre-chat form (#7275)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-06-26 10:26:06 +05:30
996325f35b feat: multiple UX improvements to labels (#7358)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2023-06-25 18:49:49 +05:30
Pranav Raj SandGitHub 4f8ce7b597 chore: Add support for dark mode logo (#7378) 2023-06-23 12:06:01 -07:00
Sojan JoseandGitHub f8e631a00c chore: Update Crowdin Config (#7375)
- add pull request titles
- skip ci for crowdin commits
2023-06-23 23:02:55 +05:30
54bf33083e chore: Update translations (#7372)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2023-06-23 16:01:14 +05:30
533f9f7fe4 fix: Email collect overriding existing contact name (#7355)
Currently, if Enable email collect box is enabled, and the user starting the chat already has an account with the same email, it will override the current account with {name} from the email name@email.com , this fixes it.

Co-authored-by: Sojan <sojan@pepalo.com>
2023-06-22 19:09:12 +05:30
82e5fc413e fix: Label Duplication in Bulk Actions (#7341)
When looping the conversations in bulk action to assign them to new labels, the existing labels in other conversations were also getting duplicated across all conversations. This PR fixes the issue. 

> In the previous implementation, new_labels is appended to the existing labels using the << operator. This operator modifies the original array instead of creating a new one, causing unwanted side effects. More specifically, new_labels is a reference to the original argument array of the method.

Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: Peter Salib <74493166+Peteraymansalib@users.noreply.github.com>
2023-06-22 18:36:58 +05:30
Pranav Raj SandGitHub 93daaea19b feat: Add a sort option for conversations waiting for a reply from an agent (#7364) 2023-06-21 13:20:39 -07:00
e6a49b5800 feat: Shows Youtube and Vimeo links as embeds [cw-1393] (#7330)
This change will render the youbtube, vimeo and .mp4 urls as embedded in the article page in the help centre.

Fixes: https://linear.app/chatwoot/issue/CW-1393/help-center-support-video-upload-in-articles

Co-authored-by: Sojan <sojan@pepalo.com>
2023-06-21 20:28:28 +05:30
1067 changed files with 22527 additions and 16396 deletions
+19 -19
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
@@ -73,7 +73,7 @@ jobs:
- run:
name: yarn
command: yarn install --cache-folder ~/.cache/yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache
- save_cache:
@@ -104,9 +104,8 @@ jobs:
fi
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
# Database setup
- run: yarn install --check-files
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
@@ -117,7 +116,7 @@ jobs:
- run:
name: Rubocop
command: bundle exec rubocop
# - run:
# name: Brakeman
# command: bundle exec brakeman
@@ -126,6 +125,21 @@ jobs:
name: eslint
command: yarn run eslint
# Run frontend tests
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
# Run rails tests
- run:
name: Run backend tests
@@ -145,20 +159,6 @@ jobs:
command: |
~/tmp/cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
- persist_to_workspace:
root: coverage
paths:
+8
View File
@@ -230,3 +230,11 @@ AZURE_APP_SECRET=
## Change these values to fine tune performance
# 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
+2
View File
@@ -74,3 +74,5 @@ yalc.lock
/yarn-error.log
yarn-debug.log*
.yarn-integrity
/storybook-static
+1 -1
View File
@@ -1 +1 @@
14.17.4
16.20.1
+1
View File
@@ -87,6 +87,7 @@ Style/ClassAndModuleChildren:
EnforcedStyle: compact
Exclude:
- 'config/application.rb'
- 'config/initializers/monkey_patches/*'
Style/MapToHash:
Enabled: false
Style/HashSyntax:
+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);
+18 -7
View File
@@ -4,7 +4,7 @@ ruby '3.2.2'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~> 7'
gem 'rails', '~> 7.0.5.1'
# 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
@@ -74,7 +74,7 @@ gem 'devise_token_auth'
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate'
gem 'administrate', '>= 0.19.0'
gem 'administrate-field-active_storage'
##--- gems for pubsub service ---##
@@ -108,9 +108,9 @@ gem 'elastic-apm', require: false
gem 'newrelic_rpm', require: false
gem 'newrelic-sidekiq-metrics', require: false
gem 'scout_apm', require: false
gem 'sentry-rails', require: false
gem 'sentry-rails', '>= 5.10.0', require: false
gem 'sentry-ruby', require: false
gem 'sentry-sidekiq', require: false
gem 'sentry-sidekiq', '>= 5.10.0', require: false
##-- background job processing --##
gem 'sidekiq'
@@ -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,16 @@ 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'
### Gems required only in specific deployment environments ###
##############################################################
@@ -187,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
@@ -210,6 +220,7 @@ group :development, :test do
gem 'bundle-audit', require: false
gem 'byebug', platform: :mri
gem 'climate_control'
gem 'debug', '~> 1.8'
gem 'factory_bot_rails'
gem 'listen'
gem 'mock_redis'
+120 -87
View File
@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
actioncable (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5)
actionpack (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activesupport (= 7.0.5)
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)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5)
actionview (= 7.0.5)
activesupport (= 7.0.5)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.1)
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)
actionpack (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5)
activesupport (= 7.0.5)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
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)
activesupport (= 7.0.5)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
globalid (>= 0.3.6)
activemodel (7.0.5)
activesupport (= 7.0.5)
activerecord (7.0.5)
activemodel (= 7.0.5)
activesupport (= 7.0.5)
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)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activesupport (= 7.0.5)
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)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5)
activesupport (7.0.5.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -105,7 +105,7 @@ GEM
activerecord (>= 6.0, < 7.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
administrate (0.18.0)
administrate (0.19.0)
actionpack (>= 5.0)
actionview (>= 5.0)
activerecord (>= 5.0)
@@ -145,6 +145,7 @@ GEM
statsd-ruby (~> 1.1)
bcrypt (3.1.18)
bindex (0.8.1)
blingfire (0.1.8)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (5.4.1)
@@ -161,9 +162,9 @@ 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.0)
connection_pool (2.4.1)
crack (0.4.5)
rexml
crass (1.0.6)
@@ -183,6 +184,9 @@ GEM
libddwaf (~> 1.8.2.0.0)
msgpack
debase-ruby_core_source (3.2.0)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
declarative (0.0.20)
devise (4.9.2)
bcrypt (~> 3.0)
@@ -361,11 +365,18 @@ GEM
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
informers (0.2.0)
blingfire (>= 0.1.7)
numo-narray
onnxruntime (>= 0.5.1)
io-console (0.6.0)
irb (1.7.2)
reline (>= 0.3.6)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.6.2)
jquery-rails (4.5.1)
jquery-rails (4.6.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
@@ -418,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)
@@ -440,17 +451,19 @@ GEM
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_portile2 (2.8.2)
minitest (5.18.0)
mini_portile2 (2.8.4)
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.4)
net-imap (0.3.6)
date
net-protocol
net-pop (0.1.2)
@@ -465,15 +478,16 @@ GEM
sidekiq
newrelic_rpm (8.16.0)
nio4r (2.5.9)
nokogiri (1.15.2)
nokogiri (1.15.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.15.2-arm64-darwin)
nokogiri (1.15.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.2-x86_64-darwin)
nokogiri (1.15.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.15.2-x86_64-linux)
nokogiri (1.15.3-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
snaky_hash (~> 2.0)
@@ -502,6 +516,14 @@ GEM
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
onnxruntime (0.7.6)
ffi
onnxruntime (0.7.6-arm64-darwin)
ffi
onnxruntime (0.7.6-x86_64-darwin)
ffi
onnxruntime (0.7.6-x86_64-linux)
ffi
openssl (3.1.0)
orm_adapter (0.5.0)
os (1.1.4)
@@ -512,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)
@@ -525,13 +548,13 @@ GEM
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.0)
rack (2.2.7)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
racc (1.7.1)
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
@@ -540,29 +563,30 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.5)
actioncable (= 7.0.5)
actionmailbox (= 7.0.5)
actionmailer (= 7.0.5)
actionpack (= 7.0.5)
actiontext (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activemodel (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
bundler (>= 1.15.0)
railties (= 7.0.5)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
railties (= 7.0.5.1)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -579,6 +603,8 @@ GEM
redis-namespace (1.10.0)
redis (>= 4)
regexp_parser (2.8.0)
reline (0.3.6)
io-console (~> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -594,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)
@@ -669,18 +697,18 @@ GEM
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.9.0)
sentry-rails (5.10.0)
railties (>= 5.0)
sentry-ruby (~> 5.9.0)
sentry-ruby (5.9.0)
sentry-ruby (~> 5.10.0)
sentry-ruby (5.10.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-sidekiq (5.9.0)
sentry-ruby (~> 5.9.0)
sentry-sidekiq (5.10.0)
sentry-ruby (~> 5.10.0)
sidekiq (>= 3.0)
sexp_processor (4.17.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.1.0)
sidekiq (7.1.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
@@ -731,7 +759,7 @@ GEM
time_diff (0.3.0)
activesupport
i18n
timeout (0.3.2)
timeout (0.4.0)
trailblazer-option (0.1.2)
twilio-ruby (5.77.0)
faraday (>= 0.9, < 3.0)
@@ -784,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
@@ -801,7 +829,7 @@ DEPENDENCIES
active_record_query_trace
activerecord-import
acts-as-taggable-on
administrate
administrate (>= 0.19.0)
administrate-field-active_storage
annotate
attr_extras
@@ -821,6 +849,7 @@ DEPENDENCIES
cypress-on-rails
database_cleaner
ddtrace
debug (~> 1.8)
devise
devise-secure_password!
devise_token_auth
@@ -846,6 +875,7 @@ DEPENDENCIES
hashie
html2text!
image_processing
informers
jbuilder
json_refs
json_schemer
@@ -856,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
@@ -867,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)
rails (~> 7.0.5.1)
redis
redis-namespace
responders
rest-client
reverse_markdown
rspec-rails
rspec_junit_formatter
rubocop
@@ -889,9 +922,9 @@ DEPENDENCIES
scout_apm
scss_lint
seed_dump
sentry-rails
sentry-rails (>= 5.10.0)
sentry-ruby
sentry-sidekiq
sentry-sidekiq (>= 5.10.0)
shoulda-matchers
sidekiq
sidekiq-cron
+15 -1
View File
@@ -30,9 +30,23 @@ burn:
bundle && yarn
run:
@if [ -f ./.overmind.sock ]; then \
echo "Overmind is already running. Use 'make force_run' to start a new instance."; \
else \
overmind start -f Procfile.dev; \
fi
force_run:
rm -f ./.overmind.sock
overmind start -f Procfile.dev
debug:
overmind connect backend
debug_worker:
overmind connect worker
docker:
docker build -t $(APP_NAME) -f ./docker/Dockerfile .
.PHONY: setup db_create db_migrate db_seed db console server burn docker run
.PHONY: setup db_create db_migrate db_seed db console server burn docker run force_run debug debug_worker
+1 -1
View File
@@ -11,7 +11,7 @@ function prepareData(data) {
function getChartOptions() {
var fontFamily =
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
'PlusJakarta,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
return {
responsive: true,
legend: { labels: { fontFamily } },
@@ -1,10 +1,10 @@
// Typography
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif !default;
$heading-font-family: $base-font-family !default;
$base-font-size: 10px !default;
$base-font-size: 16px !default;
$base-line-height: 1.5 !default;
$heading-line-height: 1.2 !default;
+11 -6
View File
@@ -48,17 +48,22 @@ class Messages::MessageBuilder
def process_emails
return unless @conversation.inbox&.inbox_type == 'Email'
cc_emails = []
cc_emails = @params[:cc_emails].gsub(/\s+/, '').split(',') if @params[:cc_emails].present?
cc_emails = process_email_string(@params[:cc_emails])
bcc_emails = process_email_string(@params[:bcc_emails])
to_emails = process_email_string(@params[:to_emails])
bcc_emails = []
bcc_emails = @params[:bcc_emails].gsub(/\s+/, '').split(',') if @params[:bcc_emails].present?
all_email_addresses = cc_emails + bcc_emails
all_email_addresses = cc_emails + bcc_emails + to_emails
validate_email_addresses(all_email_addresses)
@message.content_attributes[:cc_emails] = cc_emails
@message.content_attributes[:bcc_emails] = bcc_emails
@message.content_attributes[:to_emails] = to_emails
end
def process_email_string(email_string)
return [] if email_string.blank?
email_string.gsub(/\s+/, '').split(',')
end
def validate_email_addresses(all_emails)
+9 -8
View File
@@ -20,7 +20,7 @@ class V2::ReportBuilder
# For backward compatible with old report
def build
if %w[avg_first_response_time avg_resolution_time].include?(params[:metric])
if %w[avg_first_response_time avg_resolution_time reply_time].include?(params[:metric])
timeseries.each_with_object([]) do |p, arr|
arr << { value: p[1], timestamp: p[0].in_time_zone(@timezone).to_i, count: @grouped_values.count[p[0]] }
end
@@ -33,18 +33,19 @@ class V2::ReportBuilder
def summary
{
conversations_count: conversations_count.values.sum,
incoming_messages_count: incoming_messages_count.values.sum,
outgoing_messages_count: outgoing_messages_count.values.sum,
conversations_count: conversations.count,
incoming_messages_count: incoming_messages.count,
outgoing_messages_count: outgoing_messages.count,
avg_first_response_time: avg_first_response_time_summary,
avg_resolution_time: avg_resolution_time_summary,
resolutions_count: resolutions_count.values.sum
resolutions_count: resolutions.count,
reply_time: reply_time_summary
}
end
def conversation_metrics
if params[:type].equal?(:account)
conversations
live_conversations
else
agent_metrics.sort_by { |hash| hash[:metric][:open] }.reverse
end
@@ -89,12 +90,12 @@ class V2::ReportBuilder
email: @user.email,
thumbnail: @user.avatar_url,
availability: account_user.availability_status,
metric: conversations
metric: live_conversations
}
end
end
def conversations
def live_conversations
@open_conversations = scope.conversations.where(account_id: @account.id).open
metric = {
open: @open_conversations.count,
@@ -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)
@@ -18,7 +18,11 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
end
def authenticate_twilio
client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
client = if permitted_params[:api_key_sid].present?
Twilio::REST::Client.new(permitted_params[:api_key_sid], permitted_params[:auth_token], permitted_params[:account_sid])
else
Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
end
client.messages.list(limit: 1)
end
@@ -40,6 +44,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
@twilio_channel = Current.account.twilio_sms.create!(
account_sid: permitted_params[:account_sid],
auth_token: permitted_params[:auth_token],
api_key_sid: permitted_params[:api_key_sid],
messaging_service_sid: permitted_params[:messaging_service_sid].presence,
phone_number: phone_number,
medium: medium
@@ -52,7 +57,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
def permitted_params
params.require(:twilio_channel).permit(
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium, :api_key_sid
)
end
end
@@ -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]
@@ -63,7 +63,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
def destroy
::DeleteObjectJob.perform_later(@inbox) if @inbox.present?
::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present?
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
end
@@ -124,7 +124,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_attributes
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
:lock_to_single_conversation, :portal_id]
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name]
end
def permitted_params(channel_attributes = [])
@@ -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
@@ -17,7 +17,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
@message.update!(submitted_email: contact_email)
ContactIdentifyAction.new(
contact: @contact,
params: { email: contact_email, name: contact_name }
params: { email: contact_email, name: contact_name },
retain_original_contact_name: true
).perform
else
@message.update!(message_update_params[:message])
+10 -1
View File
@@ -1,6 +1,7 @@
class DashboardController < ActionController::Base
include SwitchLocale
before_action :set_application_pack
before_action :set_global_config
around_action :switch_locale
before_action :ensure_installation_onboarding, only: [:index]
@@ -14,7 +15,7 @@ class DashboardController < ActionController::Base
def set_global_config
@global_config = GlobalConfig.get(
'LOGO', 'LOGO_THUMBNAIL',
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
'INSTALLATION_NAME',
'WIDGET_BRAND_URL', 'TERMS_URL',
'PRIVACY_URL',
@@ -60,4 +61,12 @@ class DashboardController < ActionController::Base
GIT_SHA: GIT_HASH
}
end
def set_application_pack
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
'v3app'
else
'application'
end
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
+2 -1
View File
@@ -6,7 +6,8 @@ class ConversationFinder
latest: 'latest',
sort_on_created_at: 'sort_on_created_at',
last_user_message_at: 'last_user_message_at',
sort_on_priority: 'sort_on_priority'
sort_on_priority: 'sort_on_priority',
sort_on_waiting_since: 'sort_on_waiting_since'
}.with_indifferent_access
# assumptions
+48 -8
View File
@@ -17,21 +17,36 @@ module ReportHelper
end
def conversations_count
(get_grouped_values scope.conversations.where(account_id: account.id)).count
(get_grouped_values conversations).count
end
def incoming_messages_count
(get_grouped_values scope.messages.where(account_id: account.id).incoming.unscope(:order)).count
(get_grouped_values incoming_messages).count
end
def outgoing_messages_count
(get_grouped_values scope.messages.where(account_id: account.id).outgoing.unscope(:order)).count
(get_grouped_values outgoing_messages).count
end
def resolutions_count
object_scope = scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
conversations: { status: :resolved }).distinct
(get_grouped_values object_scope).count
(get_grouped_values resolutions).count
end
def conversations
scope.conversations.where(account_id: account.id, created_at: range)
end
def incoming_messages
scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order)
end
def outgoing_messages
scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order)
end
def resolutions
scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
conversations: { status: :resolved }, created_at: range).distinct
end
def avg_first_response_time
@@ -41,6 +56,13 @@ module ReportHelper
grouped_reporting_events.average(:value)
end
def reply_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'reply_time', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value)
end
def avg_resolution_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
@@ -51,17 +73,35 @@ module ReportHelper
def avg_resolution_time_summary
reporting_events = scope.reporting_events
.where(name: 'conversation_resolved', account_id: account.id, created_at: range)
avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
avg_rt = if params[:business_hours].present?
reporting_events.average(:value_in_business_hours)
else
reporting_events.average(:value)
end
return 0 if avg_rt.blank?
avg_rt
end
def reply_time_summary
reporting_events = scope.reporting_events
.where(name: 'reply_time', account_id: account.id, created_at: range)
reply_time = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if reply_time.blank?
reply_time
end
def avg_first_response_time_summary
reporting_events = scope.reporting_events
.where(name: 'first_response', account_id: account.id, created_at: range)
avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
avg_frt = if params[:business_hours].present?
reporting_events.average(:value_in_business_hours)
else
reporting_events.average(:value)
end
return 0 if avg_frt.blank?
+11
View File
@@ -4,6 +4,7 @@
id="app"
class="app-wrapper app-root"
:class="{ 'app-rtl--wrapper': isRTLView }"
:dir="isRTLView ? 'rtl' : 'ltr'"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
@@ -34,6 +35,7 @@ import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer';
import rtlMixin from 'shared/mixins/rtlMixin';
import { setColorTheme } from './helper/themeHelper';
import {
registerSubscription,
verifyServiceWorkerExistence,
@@ -89,9 +91,18 @@ export default {
},
},
mounted() {
this.initializeColorTheme();
this.listenToThemeChanges();
this.setLocale(window.chatwootConfig.selectedLocale);
},
methods: {
initializeColorTheme() {
setColorTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
},
listenToThemeChanges() {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
mql.onchange = e => setColorTheme(e.matches);
},
setLocale(locale) {
this.$root.$i18n.locale = locale;
},
@@ -18,6 +18,10 @@ class CacheEnabledApiClient extends ApiClient {
return this.getFromCache();
}
return this.getFromNetwork();
}
getFromNetwork() {
return axios.get(this.url);
}
@@ -32,7 +36,12 @@ class CacheEnabledApiClient extends ApiClient {
}
async getFromCache() {
await this.dataManager.initDb();
try {
// IDB is not supported in Firefox private mode: https://bugzilla.mozilla.org/show_bug.cgi?id=781982
await this.dataManager.initDb();
} catch {
return this.getFromNetwork();
}
const { data } = await axios.get(
`/api/v1/accounts/${this.accountIdFromRoute}/cache_keys`
@@ -55,16 +64,22 @@ class CacheEnabledApiClient extends ApiClient {
}
async refetchAndCommit(newKey = null) {
await this.dataManager.initDb();
const response = await axios.get(this.url);
this.dataManager.replace({
modelName: this.cacheModelName,
data: this.extractDataFromResponse(response),
});
const response = await this.getFromNetwork();
await this.dataManager.setCacheKeys({
[this.cacheModelName]: newKey,
});
try {
await this.dataManager.initDb();
this.dataManager.replace({
modelName: this.cacheModelName,
data: this.extractDataFromResponse(response),
});
await this.dataManager.setCacheKeys({
[this.cacheModelName]: newKey,
});
} catch {
// Ignore error
}
return response;
}
-75
View File
@@ -3,47 +3,11 @@
import Cookies from 'js-cookie';
import endPoints from './endPoints';
import {
setAuthCredentials,
clearCookiesOnLogout,
deleteIndexedDBOnLogout,
} from '../store/utils/api';
export default {
login(creds) {
return new Promise((resolve, reject) => {
axios
.post('auth/sign_in', creds)
.then(response => {
setAuthCredentials(response);
resolve(response.data);
})
.catch(error => {
reject(error.response);
});
});
},
register(creds) {
const urlData = endPoints('register');
const fetchPromise = new Promise((resolve, reject) => {
axios
.post(urlData.url, {
account_name: creds.accountName.trim(),
user_full_name: creds.fullName.trim(),
email: creds.email,
password: creds.password,
h_captcha_client_response: creds.hCaptchaClientResponse,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error);
});
});
return fetchPromise;
},
validityCheck() {
const urlData = endPoints('validityCheck');
return axios.get(urlData.url);
@@ -73,45 +37,6 @@ export default {
}
return false;
},
verifyPasswordToken({ confirmationToken }) {
return new Promise((resolve, reject) => {
axios
.post('auth/confirmation', {
confirmation_token: confirmationToken,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error.response);
});
});
},
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
return new Promise((resolve, reject) => {
axios
.put('auth/password', {
reset_password_token: resetPasswordToken,
password_confirmation: confirmPassword,
password,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error.response);
});
});
},
resetPassword({ email }) {
const urlData = endPoints('resetPassword');
return axios.post(urlData.url, { email });
},
profileUpdate({
password,
password_confirmation,
@@ -10,6 +10,7 @@ export const buildCreatePayload = ({
files,
ccEmails = '',
bccEmails = '',
toEmails = '',
templateParams,
}) => {
let payload;
@@ -25,6 +26,9 @@ export const buildCreatePayload = ({
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
payload.append('bcc_emails', bccEmails);
if (toEmails) {
payload.append('to_emails', toEmails);
}
} else {
payload = {
content: message,
@@ -33,6 +37,7 @@ export const buildCreatePayload = ({
content_attributes: contentAttributes,
cc_emails: ccEmails,
bcc_emails: bccEmails,
to_emails: toEmails,
template_params: templateParams,
};
}
@@ -53,6 +58,7 @@ class MessageApi extends ApiClient {
files,
ccEmails = '',
bccEmails = '',
toEmails = '',
templateParams,
}) {
return axios({
@@ -66,6 +72,7 @@ class MessageApi extends ApiClient {
files,
ccEmails,
bccEmails,
toEmails,
templateParams,
}),
});
+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}`);
}
@@ -2,18 +2,62 @@
import ApiClient from '../ApiClient';
/**
* Represents the data object for a OpenAI hook.
* @typedef {Object} ConversationMessageData
* @property {string} [tone] - The tone of the message.
* @property {string} [content] - The content of the message.
* @property {string} [conversation_display_id] - The display ID of the conversation (optional).
*/
/**
* A client for the OpenAI API.
* @extends ApiClient
*/
class OpenAIAPI extends ApiClient {
/**
* Creates a new OpenAIAPI instance.
*/
constructor() {
super('integrations', { accountScoped: true });
/**
* The conversation events supported by the API.
* @type {string[]}
*/
this.conversation_events = [
'summarize',
'reply_suggestion',
'label_suggestion',
];
/**
* The message events supported by the API.
* @type {string[]}
*/
this.message_events = ['rephrase'];
}
/**
* Processes an event using the OpenAI API.
* @param {Object} options - The options for the event.
* @param {string} [options.type='rephrase'] - The type of event to process.
* @param {string} [options.content] - The content of the event.
* @param {string} [options.tone] - The tone of the event.
* @param {string} [options.conversationId] - The ID of the conversation to process the event for.
* @param {string} options.hookId - The ID of the hook to use for processing the event.
* @returns {Promise} A promise that resolves with the result of the event processing.
*/
processEvent({ type = 'rephrase', content, tone, conversationId, hookId }) {
/**
* @type {ConversationMessageData}
*/
let data = {
tone,
content,
};
if (type === 'reply_suggestion' || type === 'summarize') {
if (this.conversation_events.includes(type)) {
data = {
conversation_display_id: conversationId,
};
@@ -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(
@@ -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: 4rem;
@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;
}
}
@@ -8,7 +8,7 @@
}
select {
height: 4.0rem;
height: 2.5rem;
}
.card {
@@ -16,19 +16,6 @@ select {
padding: var(--space-normal);
}
.button-wrapper .button.grey-btn {
margin-left: var(--space-normal);
}
.tooltip {
background-color: var(--black-transparent);
border-radius: $space-smaller;
font-size: $font-size-mini;
max-width: var(--space-giga);
padding: $space-smaller $space-small;
z-index: 999;
}
code {
border: 0;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
@@ -39,6 +26,7 @@ code {
background: $color-background;
border-radius: var(--border-radius-large);
padding: $space-two;
@apply bg-slate-50 dark:bg-slate-600 text-slate-800 dark:text-slate-100;
}
}
@@ -48,7 +48,7 @@
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 10px;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
@@ -63,7 +63,7 @@ $black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'Inter',
$body-font-family: 'PlusJakarta',
-apple-system,
system-ui,
BlinkMacSystemFont,
@@ -117,7 +117,7 @@ $header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-margin-bottom: 0.3125rem;
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
@@ -133,7 +133,7 @@ $header-styles: (small: ("h1": ("font-size": 24),
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.45;
$paragraph-lineheight: 1.65;
$paragraph-margin-bottom: var(--space-small);
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
@@ -153,11 +153,11 @@ $list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 1.25rem;
$list-nested-side-margin: 1.25rem;
$defnlist-margin-bottom: 1rem;
$list-side-margin: 0.78125rem;
$list-nested-side-margin: 0.78125rem;
$defnlist-margin-bottom: 0.6875rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.3rem;
$defnlist-term-margin-bottom: 0.1875rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
@@ -179,9 +179,9 @@ $lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.2rem;
$subheader-margin-bottom: 0.5rem;
$stat-font-size: 2.5rem;
$subheader-margin-top: 0.125rem;
$subheader-margin-bottom: 0.3125rem;
$stat-font-size: 1.5625rem;
// 6. Abide
// --------
@@ -202,11 +202,11 @@ $accordion-plusminus: true;
$accordion-title-font-size: rem-calc(12);
$accordion-item-color: $primary-color;
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 1.25rem 1rem;
$accordion-item-padding: 0.78125rem 0.625rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: $body-font-color;
$accordion-content-padding: 1rem;
$accordion-content-padding: 0.625rem;
// 8. Accordion Menu
// -----------------
@@ -234,7 +234,7 @@ $breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.75rem;
$breadcrumbs-item-margin: 0.46875rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
@@ -275,8 +275,8 @@ $buttongroup-radius-on-each: false;
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 1rem 0;
$callout-padding: 1rem;
$callout-margin: 0 0 0.625rem 0;
$callout-padding: 0.625rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
@@ -320,10 +320,10 @@ $drilldown-background: $white;
// 17. Dropdown
// ------------
$dropdown-padding: 1rem;
$dropdown-padding: 0.625rem;
$dropdown-background: $body-background;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 1rem;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (tiny: 100px,
@@ -354,7 +354,7 @@ $helptext-font-style: italic;
$input-prefix-color: $color-body;
$input-prefix-background: var(--b-100);
$input-prefix-border: 1px solid $color-border;
$input-prefix-padding: 1rem;
$input-prefix-padding: 0.625rem;
$form-label-color: $color-body;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $font-weight-medium;
@@ -406,14 +406,14 @@ $menu-margin-nested: $space-medium;
$menu-item-padding: $space-slab;
$menu-item-color-active: $white;
$menu-item-background-active: $color-background;
$menu-icon-spacing: 0.25rem;
$menu-icon-spacing: 0.15625rem;
$menu-item-background-hover: $light-gray;
$menu-border: $light-gray;
// 23. Meter
// ---------
$meter-height: 1rem;
$meter-height: 0.625rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
@@ -423,11 +423,11 @@ $meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (small: 23rem,
medium: 23rem,
$offcanvas-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-vertical-sizes: (small: 23rem,
medium: 23rem,
$offcanvas-vertical-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
@@ -445,14 +445,14 @@ $maincontent-class: 'off-canvas-content';
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;
$orbit-bullet-diameter: 0.75rem;
$orbit-bullet-margin: 0.0625rem;
$orbit-bullet-margin-top: 0.5rem;
$orbit-bullet-margin-bottom: 0.5rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 1rem;
$orbit-caption-padding: 0.625rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 1rem;
$orbit-control-padding: 0.625rem;
$orbit-control-zindex: 10;
// 26. Pagination
@@ -476,7 +476,7 @@ $pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 1rem;
$progress-height: 0.625rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
@@ -504,13 +504,13 @@ $reveal-overlay-background: rgba($black, 0.45);
// 30. Slider
// ----------
$slider-width-vertical: 0.5rem;
$slider-width-vertical: 0.3125rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.5rem;
$slider-height: 0.3125rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 1.4rem;
$slider-handle-width: 1.4rem;
$slider-handle-height: 0.875rem;
$slider-handle-width: 0.875rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
@@ -569,7 +569,7 @@ $tab-expand-max: 6;
$tab-content-background: transparent;
$tab-content-border: transparent;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 1rem;
$tab-content-padding: 0.625rem;
// 34. Thumbnail
// -------------
@@ -586,11 +586,11 @@ $thumbnail-radius: $global-radius;
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.5rem;
$titlebar-padding: 0.3125rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.25rem;
$titlebar-icon-spacing: 0.15625rem;
// 36. Tooltip
// -----------
@@ -599,19 +599,19 @@ $has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.75rem;
$tooltip-padding: 0.46875rem;
$tooltip-font-size: $font-size-mini;
$tooltip-pip-width: 0.75rem;
$tooltip-pip-width: 0.46875rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 37. Top Bar
// -----------
$topbar-padding: 0.5rem;
$topbar-padding: 0.3125;
$topbar-background: $light-gray;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;
@@ -1,5 +1,5 @@
.bg-light {
@include background-light;
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {
@@ -22,43 +22,43 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin border-normal() {
border: 1px solid $color-border;
@apply border border-slate-50 dark:border-slate-700;
}
@mixin border-normal-left() {
border-left: 1px solid $color-border;
@apply border-l border-slate-50 dark:border-slate-700;
}
@mixin border-normal-top() {
border-top: 1px solid $color-border;
@apply border-t border-slate-50 dark:border-slate-700;
}
@mixin border-normal-right() {
border-right: 1px solid $color-border;
@apply border-r border-slate-50 dark:border-slate-700;
}
@mixin border-normal-bottom() {
border-bottom: 1px solid $color-border;
@apply border-b border-slate-50 dark:border-slate-700;
}
@mixin border-light() {
border: 1px solid $color-border-light;
@apply border border-slate-25 dark:border-slate-700;
}
@mixin border-light-left() {
border-left: 1px solid $color-border-light;
@apply border-l border-slate-25 dark:border-slate-700;
}
@mixin border-light-top() {
border-top: 1px solid $color-border-light;
@apply border-t border-slate-25 dark:border-slate-700;
}
@mixin border-light-right() {
border-right: 1px solid $color-border-light;
@apply border-r border-slate-25 dark:border-slate-700;
}
@mixin border-light-bottom() {
border-bottom: 1px solid $color-border-light;
@apply border-b border-slate-25 dark:border-slate-700;
}
// background
@@ -67,11 +67,11 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin background-light() {
background: $color-background-light;
@apply bg-slate-50 dark:bg-slate-800;
}
@mixin background-white() {
background: $color-white;
@apply bg-white dark:bg-slate-900;
}
// input form
@@ -237,8 +237,8 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
white-space: nowrap;
}
@mixin three-column-grid($column-one-width: 25.6rem,
$column-three-width: 25.6rem) {
@mixin three-column-grid($column-one-width: 16rem,
$column-three-width: 16rem) {
width: 100%;
height: 100%;
display: grid;
+1 -137
View File
@@ -1,90 +1,6 @@
.app-rtl--wrapper {
direction: rtl;
// Primary sidebar
.primary--sidebar {
border-left: 1px solid var(--s-50);
border-right: 0;
.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;
}
}
}
// Secondary sidebar
.secondary-sidebar {
.secondary-menu {
border-left: 1px solid var(--s-50);
border-right: 0;
.nested.vertical.menu {
.badge--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.menu-label {
text-align: right;
}
}
.secondary-menu--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.account-context--group .account-context--switch-group {
--overlay-shadow: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
background-image: var(--overlay-shadow);
}
// Help center sidebar
.sidebar-header--wrap .header-title--wrap {
margin-left: unset;
margin-right: var(--space-small);
}
}
}
// Woot button
.button {
.icon--emoji + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon--font + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
}
// 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);
@@ -133,23 +49,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 {
@@ -197,11 +96,6 @@
}
}
// Conversation sidebar toggle button
.sidebar-toggle--button {
transform: rotate(180deg);
}
// Conversation sidebar close button
.close-button--rtl {
transform: rotate(180deg);
@@ -345,31 +239,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;
@@ -439,7 +308,7 @@
}
span {
--minus-space-one-point-five: -1.5rem;
--minus-space-one-point-five: -0.9375rem;
&.active {
transform: translate(
@@ -473,11 +342,6 @@
direction: initial;
}
.inbox--name .inbox--icon {
margin-left: var(--space-micro);
margin-right: 0;
}
.colorpicker--chrome {
direction: initial;
}
@@ -29,4 +29,5 @@ a {
p {
font-size: $font-size-small;
word-spacing: .12em;
}
@@ -43,17 +43,13 @@
}
.border-right {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
.border-left {
border-left: 1px solid var(--color-border);
}
.bg-white {
background-color: var(--white);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
@@ -1,30 +1,30 @@
// Font sizes
$font-size-nano: 0.8rem;
$font-size-micro: 1.0rem;
$font-size-mini: 1.2rem;
$font-size-small: 1.4rem;
$font-size-default: 1.6rem;
$font-size-medium: 1.8rem;
$font-size-large: 2.2rem;
$font-size-big: 2.4rem;
$font-size-bigger: 3.0rem;
$font-size-mega: 3.4rem;
$font-size-giga: 4.0rem;
$font-size-nano: 0.5rem;
$font-size-micro: 0.675rem;
$font-size-mini: 0.75rem;
$font-size-small: 0.875rem;
$font-size-default: 1rem;
$font-size-medium: 1.125rem;
$font-size-large: 1.375rem;
$font-size-big: 1.5rem;
$font-size-bigger: 1.75rem;
$font-size-mega: 2.125rem;
$font-size-giga: 2.5rem;
// spaces
$zero: 0;
$space-micro: 0.2rem;
$space-smaller: 0.4rem;
$space-small: 0.8rem;
$space-one: 1rem;
$space-slab: 1.2rem;
$space-normal: 1.6rem;
$space-two: 2.0rem;
$space-medium: 2.4rem;
$space-large: 3.2rem;
$space-larger: 4.8rem;
$space-jumbo: 6.4rem;
$space-mega: 10.0rem;
$space-micro: 0.125rem;
$space-smaller: 0.25rem;
$space-small: 0.5rem;
$space-one: 0.675rem;
$space-slab: 0.75rem;
$space-normal: 1rem;
$space-two: 1.25rem;
$space-medium: 1.5rem;
$space-large: 2rem;
$space-larger: 3rem;
$space-jumbo: 4rem;
$space-mega: 6.25rem;
// font-weight
$font-weight-feather: 100;
@@ -35,8 +35,8 @@ $font-weight-bold: 600;
$font-weight-black: 700;
//Navbar
$nav-bar-width: 23rem;
$header-height: 5.6rem;
$nav-bar-width: 14.375rem;
$header-height: 3.5rem;
$woot-logo-padding: $space-large $space-two;
@@ -71,20 +71,20 @@ $color-primary-light: #c7e3ff;
$color-primary-dark: darken($color-woot, 20%);
// Thumbnail
$thumbnail-radius: 4rem;
$thumbnail-radius: 2.5rem;
// chat-header
$conv-header-height: 4rem;
$conv-header-height: 2.5rem;
// Inbox List
$inbox-thumb-size: 4.8rem;
$inbox-thumb-size: 3rem;
// Spinner
$spinkit-spinner-color: $color-white !default;
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
$spinkit-size: 1.6rem !default;
$spinkit-spinner-margin: 0 0 0 1rem !default;
$spinkit-size: 1rem !default;
// Snackbar default
$woot-snackbar-bg: #323232;
@@ -101,5 +101,5 @@ $ionicons-font-path: '~ionicons/fonts';
$transition-ease-in: all 0.250s ease-in;
:root {
--dashboard-app-tabs-height: 3.9rem;
--dashboard-app-tabs-height: 2.4375rem;
}
@@ -1,4 +1,8 @@
@import 'shared/assets/fonts/inter';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@@ -67,3 +71,7 @@
@import 'plugins/dropdown';
@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: 4.6rem;
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: 4.4rem;
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: 4.4rem;
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: 4.0rem;
.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: 3.8rem;
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: 4.8rem;
.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;
}
}
@@ -1,4 +1,5 @@
@import 'shared/assets/fonts/inter';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@@ -9,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';
@@ -29,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,3 +1,3 @@
@import 'shared/assets/fonts/inter';
@import 'shared/assets/fonts/plus-jakarta';
@import '../variables';
@import '~shared/assets/stylesheets/ionicons';
@@ -1,173 +1,112 @@
.settings {
overflow: auto;
}
// Conversation header - Light BG
.settings-header {
@include background-white;
@include flex;
@include flex-align($x: justify, $y: middle);
border-bottom: 1px solid var(--s-50);
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 background-white;
@include border-light;
@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: 10rem;
width: 10rem;
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,207 +1,201 @@
$default-button-height: 4.0rem;
.button {
align-items: center;
display: inline-flex;
height: $default-button-height;
margin-bottom: 0;
@apply items-center inline-flex h-10 mb-0;
.button__content {
width: 100%;
@apply w-full;
img,
svg {
@apply inline-block;
}
}
.spinner {
padding: 0 var(--space-small);
@apply px-2 py-0;
}
.icon--emoji+.button__content {
padding-left: var(--space-small);
.icon--emoji + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
.icon--font+.button__content {
padding-left: var(--space-small);
.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 {
padding-left: var(--space-small);
width: auto;
.icon + .button__content {
@apply pl-2 w-auto rtl:pr-2 rtl:pl-0;
}
&.expanded {
display: flex;
justify-content: center;
@apply flex justify-center text-center;
}
&.round {
border-radius: $space-larger;
@apply rounded-full;
}
// @TODO Use with link
&.compact {
padding-bottom: 0;
padding-top: 0;
@apply pb-0 pt-0;
}
&.hollow {
border-color: var(--s-200);
color: var(--w-700);
@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 {
border-color: var(--s-200);
color: var(--s-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 {
border-color: var(--s-200);
color: var(--g-700);
@apply text-green-700 dark:text-green-100 hover:bg-green-50 dark:hover:bg-green-800;
}
&.alert {
border-color: var(--s-200);
color: var(--r-700);
@apply text-red-700 dark:text-red-100 hover:bg-red-50 dark:hover:bg-red-800;
}
&.warning {
border-color: var(--s-200);
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-100 hover:bg-yellow-50 dark:hover:bg-yellow-800;
}
&:hover {
background: var(--s-75);
border-color: var(--s-100);
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
&.secondary {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
}
&.success {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-green-800 dark:text-green-100;
}
&.alert {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-red-700 dark:text-red-100;
}
&.warning {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-700;
}
}
}
// Smooth style
&.smooth {
@include button-style(var(--w-50), var(--w-100), var(--w-700));
@apply bg-woot-50 dark:bg-woot-800 text-woot-700 dark:text-woot-100 hover:text-woot-700 dark:hover:text-woot-700 hover:bg-woot-100 dark:hover:bg-woot-900;
&.secondary {
@include button-style(var(--s-50), var(--s-100), var(--s-700));
@apply bg-slate-50 dark:bg-slate-700 text-slate-700 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800;
}
&.success {
@include button-style(var(--g-50), var(--g-100), var(--g-700));
@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 {
@include button-style(var(--r-50), var(--r-100), var(--r-700));
@apply bg-red-50 dark:bg-red-700 dark:bg-opacity-50 text-red-700 dark:text-red-100 hover:bg-red-100 dark:hover:bg-red-800 dark:hover:bg-opacity-30;
}
&.warning {
@include button-style(var(--y-100), var(--y-200), var(--y-700));
@apply bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700 hover:bg-yellow-200 dark:hover:bg-yellow-200;
}
}
&.clear {
color: var(--w-700);
@apply text-woot-500 dark:text-woot-500;
&.secondary {
color: var(--s-700);
@apply text-slate-700 dark:text-slate-100;
}
&.success {
color: var(--g-700);
@apply text-green-700 dark:text-green-100;
}
&.alert {
color: var(--r-700);
@apply text-red-700 dark:text-red-100;
}
&.warning {
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-600;
}
&:hover {
background: var(--w-50);
@apply hover:bg-woot-50 dark:hover:bg-woot-900/50 hover:text-woot-500 dark:hover:text-woot-100;
&.secondary {
background: var(--s-50);
@apply hover:bg-slate-50 dark:hover:bg-slate-700 hover:text-slate-800 dark:hover:text-slate-100;
}
&.success {
background: var(--g-50);
@apply hover:bg-green-50 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
background: var(--r-50);
@apply hover:bg-red-50 dark:hover:bg-red-800 hover:text-red-700 dark:hover:text-red-100;
}
&.warning {
background: var(--y-50);
@apply hover:bg-yellow-100 dark:hover:bg-yellow-800 hover:text-yellow-700 dark:hover:text-yellow-600;
}
}
&:active {
&.secondary {
@apply active:bg-slate-100 dark:active:bg-slate-900;
}
}
&:focus {
&.secondary {
@apply focus:bg-slate-50 dark:focus:bg-slate-700;
}
}
}
// Sizes
&.tiny {
height: var(--space-medium);
@apply h-6;
.icon+.button__content {
padding-left: var(--space-micro);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.small {
height: var(--space-large);
padding-bottom: var(--space-smaller);
padding-top: var(--space-smaller);
@apply h-8 pb-1 pt-1;
.icon+.button__content {
padding-left: var(--space-smaller);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.large {
height: var(--space-larger);
@apply h-12;
}
&.button--only-icon {
justify-content: center;
padding-left: 0;
padding-right: 0;
width: $default-button-height;
@apply justify-center pl-0 pr-0 w-10;
&.tiny {
width: var(--space-medium);
@apply w-6;
}
&.small {
width: var(--space-large);
@apply w-8;
}
&.large {
width: var(--space-larger);
@apply w-12;
}
}
&.link {
height: auto;
margin: 0;
padding: 0;
@apply h-auto m-0 p-0;
&:hover {
text-decoration: underline;
@apply underline;
}
}
}
@@ -1,71 +1 @@
$resolve-button-width: 13.2rem;
// 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: 21.6rem;
.icon {
color: $medium-gray;
font-size: $font-size-default;
line-height: 3.8rem;
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;
margin-top: var(--space-small);
@include breakpoint(medium up) {
margin-top: 0;
}
&.has-open-sidebar {
justify-content: flex-end;
}
}
// File to be removed
@@ -10,135 +10,7 @@
}
.conversation {
@include flex;
@include flex-shrink;
border-bottom: 1px solid transparent;
border-left: var(--space-micro) solid transparent;
border-top: 1px 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-bottom-color: var(--color-border-light);
border-left-color: var(--color-woot);
border-top-color: var(--color-border-light);
.conversation--details {
border-top-color: transparent;
}
+ .conversation .conversation--details {
border-top-color: transparent;
}
}
&:first-child {
.conversation--details {
border-top-color: transparent;
}
}
&:last-child {
.conversation--details {
border-bottom-color: var(--color-border-light);
}
}
.conversation--details {
@include border-light-bottom;
@include border-light-top;
border-bottom-color: transparent;
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: 27rem;
}
.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: 49.6rem;
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,15 +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;
@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,22 +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;
}
}
@@ -1,117 +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: 6rem;
@apply max-h-[3.75rem];
}
}
.modal-container {
@include normal-shadow;
background-color: $color-white;
border-radius: $space-smaller;
max-height: 100%;
overflow: auto;
position: relative;
width: 60rem;
@apply shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem];
&.medium {
max-width: 80%;
width: 90rem;
@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;
}
@@ -29,7 +29,7 @@
// Override min-height : 50px in foundation
//
max-height: $space-mega * 2.4;
min-height: 4.8rem;
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
@@ -40,20 +40,20 @@
margin: 0;
max-height: $space-mega * 2.4;
// Override min-height : 50px in foundation
min-height: 4.8rem;
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
}
&.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;
}
}
}
@@ -58,31 +58,3 @@
text-transform: capitalize;
}
}
.report-bar {
@include background-white;
@include border-light;
margin: var(--space-minus-micro) 0;
padding: var(--space-small) var(--space-medium);
.chart-container {
@include flex;
@include flex-align(center, middle);
flex-direction: column;
div {
width: 100%;
}
.empty-state {
color: $color-gray;
font-size: var(--font-size-default);
margin: var(--space-jumbo);
}
.business-hours {
margin: var(--space-normal);
text-align: center;
}
}
}
@@ -22,18 +22,6 @@
margin: 0 var(--space-small);
}
.business-hours {
align-items: center;
display: flex;
justify-content: flex-start;
margin-left: auto;
padding-right: var(--space-normal);
}
.business-hours-text {
font-size: var(--font-size-small);
margin: 0 var(--space-small);
}
.switch {
margin-bottom: var(--space-zero);
@@ -47,7 +47,7 @@
}
.dropdown-pane {
bottom: 6rem;
bottom: 3.75rem;
display: block;
visibility: visible;
width: fit-content;
@@ -1,7 +1,7 @@
.ui-snackbar-container {
left: 0;
margin: 0 auto;
max-width: 40rem;
max-width: 25rem;
overflow: hidden;
position: absolute;
right: 0;
@@ -16,9 +16,9 @@
border-radius: $space-smaller;
display: inline-flex;
margin-bottom: $space-small;
max-width: 40rem;
min-height: 3rem;
min-width: 24rem;
max-width: 25rem;
min-height: 1.875rem;
min-width: 15rem;
padding: $space-slab $space-medium;
text-align: left;
}
@@ -31,7 +31,7 @@
.ui-snackbar-action {
margin-left: auto;
padding-left: 3rem;
padding-left: 1.875rem;
button {
background: none;
@@ -1,42 +1 @@
.loading-state {
padding: $space-jumbo $space-smaller;
.message {
color: $color-gray;
display: block;
text-align: center;
width: 100%;
}
.spinner {
float: none;
top: -$space-smaller;
}
}
// 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,22 +1,19 @@
table {
border-spacing: 0;
font-size: var(--font-size-small);
@apply border-spacing-0 text-sm;
thead {
th {
font-weight: var(--font-weight-bold);
text-align: left;
text-transform: uppercase;
@apply font-semibold tracking-[1px] text-left uppercase text-slate-900 dark:text-slate-200;
}
}
tbody {
tr {
border-bottom: 1px solid var(--color-border-light);
@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;
}
}
}
@@ -24,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: 5rem;
width: 5rem;
@apply rounded-full h-[3.125rem] w-[3.125rem];
}
.button-wrapper {
@include flex-align(left, null);
@include flex;
flex-direction: row;
min-width: 20rem;
@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-li {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200 border-slate-75 dark:border-slate-700;
}
.ve-pagination-goto-input {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200;
}
}
@@ -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,47 +128,46 @@
/>
<div
ref="activeConversation"
class="conversations-list"
:class="{ 'is-context-menu-open': isContextMenuOpen }"
class="conversations-list flex-1"
:class="{ 'overflow-hidden': isContextMenuOpen }"
>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
<div>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
</div>
<div v-if="chatListLoading" class="text-center">
<span class="spinner" />
<span class="spinner mt-4 mb-4" />
</div>
<woot-button
v-if="!hasCurrentPageEndReached && !chatListLoading"
variant="clear"
size="expanded"
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>
@@ -954,72 +958,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/woot';
.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(32rem, 4vw + 34rem, 44rem);
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: 1.4rem;
font-size: var(--font-size-small);
font-weight: 400;
margin-bottom: 1rem;
margin-top: -1.6rem;
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>
@@ -52,7 +52,8 @@ export default {
},
computed: {
modalContainerClassName() {
let className = 'modal-container skip-context-menu';
let className =
'modal-container bg-white dark:bg-slate-800 skip-context-menu';
if (this.fullWidth) {
return `${className} modal-container--full-width`;
}
@@ -106,7 +107,7 @@ export default {
.modal-container {
border-radius: 0;
height: 100%;
width: 48rem;
width: 30rem;
}
}
.modal-big {
@@ -1,12 +1,22 @@
<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 class="page-sub-title">
<h2
ref="modalHeaderTitle"
class="text-slate-800 text-lg dark:text-slate-100"
>
{{ headerTitle }}
</h2>
<p v-if="headerContent" class="small-12 column wrap-content">
<p
v-if="headerContent"
ref="modalHeaderContent"
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>
@@ -36,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>
@@ -77,7 +77,7 @@ export default {
@import '~dashboard/assets/scss/mixins';
.ui-notification-container {
max-width: 40rem;
max-width: 25rem;
position: absolute;
right: var(--space-normal);
top: var(--space-normal);
@@ -94,7 +94,7 @@ export default {
border-radius: var(--border-radius-medium);
box-shadow: var(--shadow-large);
min-width: 24rem;
min-width: 15rem;
padding: var(--space-normal);
}
@@ -1,20 +1,28 @@
<template>
<div class="row settings--section">
<div class="medium-4 small-12 title--section">
<p 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">
<slot name="subTitle">
<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>
@@ -25,11 +33,15 @@ export default {
props: {
title: {
type: String,
required: true,
default: '',
},
subTitle: {
type: String,
required: true,
default: '',
},
showBorder: {
type: Boolean,
default: true,
},
note: {
type: String,
@@ -38,27 +50,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.settings--section {
border-bottom: 1px solid $color-border;
display: flex;
padding: $space-normal $space-normal $space-normal 0;
.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,10 +1,10 @@
<template>
<woot-button
size="small"
:size="size"
variant="clear"
color-scheme="secondary"
class="-ml-3 text-black-900 dark:text-slate-300"
icon="list"
class="toggle-sidebar"
@click="onMenuItemClick"
/>
</template>
@@ -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);
@@ -20,8 +26,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.toggle-sidebar {
margin-left: var(--space-minus-small);
}
</style>
@@ -0,0 +1,11 @@
<template>
<kbd class="hotkey">
<slot />
</kbd>
</template>
<style lang="scss">
kbd.hotkey {
@apply inline-flex p-0.5 leading-[0.625rem] min-w-[1rem] tracking-wide uppercase text-slate-800 dark:text-slate-100 bg-slate-50 dark:bg-slate-600 text-xxs border border-solid border-slate-75 dark:border-slate-600 rounded 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"
@@ -50,15 +50,6 @@
>
<woot-dropdown-menu>
<woot-dropdown-item v-if="!isPending">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="book-clock"
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
</woot-button>
<woot-button
variant="clear"
color-scheme="secondary"
@@ -69,6 +60,17 @@
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item v-if="!isPending">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="book-clock"
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
</woot-button>
</woot-dropdown-item>
</woot-dropdown-menu>
</div>
<woot-modal
@@ -249,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: 4.2rem;
margin-top: var(--space-micro);
right: 0;
max-width: 20rem;
min-width: 15.6rem;
@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>
@@ -4,7 +4,7 @@
<woot-dropdown-item
v-for="status in availabilityStatuses"
:key="status.value"
class="status-items"
class="flex items-baseline"
>
<woot-button
size="small"
@@ -18,23 +18,25 @@
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider />
<woot-dropdown-item class="auto-offline--toggle">
<div class="info-wrap">
<woot-dropdown-item class="m-0 flex items-center justify-between p-2">
<div class="flex items-center">
<fluent-icon
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
icon="info"
size="14"
class="info-icon"
class="mt-px"
/>
<span class="auto-offline--text">
<span
class="my-0 mx-1 text-xs font-medium text-slate-600 dark:text-slate-100"
>
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
</span>
</div>
<woot-switch
size="small"
class="auto-offline--switch"
class="mt-px mx-1 mb-0"
:value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
@@ -138,71 +140,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.status {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: var(--space-micro) var(--space-smaller);
}
.status-view {
display: flex;
align-items: baseline;
& &--title {
color: var(--b-600);
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-left: var(--space-small);
&:first-letter {
text-transform: capitalize;
}
}
}
.status-change {
.dropdown-pane {
top: -132px;
right: var(--space-normal);
}
.status-items {
display: flex;
align-items: baseline;
}
}
.auto-offline--toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding: var(--space-smaller);
margin: 0;
.info-wrap {
display: flex;
align-items: center;
}
.info-icon {
margin-top: -1px;
}
.auto-offline--switch {
margin: -1px var(--space-micro) 0;
}
.auto-offline--text {
margin: 0 var(--space-smaller);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
color: var(--s-700);
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<aside class="woot-sidebar">
<aside class="h-full flex">
<primary-sidebar
:logo-source="globalConfig.logoThumbnail"
:installation-name="globalConfig.installationName"
@@ -11,22 +11,21 @@
@key-shortcut-modal="toggleKeyShortcutModal"
@open-notification-panel="openNotificationPanel"
/>
<div class="secondary-sidebar">
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</div>
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</aside>
</template>
@@ -214,87 +213,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.woot-sidebar {
background: var(--white);
display: flex;
min-height: 0;
height: 100%;
width: fit-content;
}
</style>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.account-selector--modal {
.modal-container {
width: 40rem;
}
}
.secondary-sidebar {
overflow-y: auto;
height: 100%;
}
.account-selector {
cursor: pointer;
padding: $space-small $space-large;
.selected--account {
margin-top: -$space-smaller;
& + .account--details {
padding-left: $space-normal - $space-micro;
}
}
.account--details {
padding-left: $space-large + $space-smaller;
}
&:last-child {
margin-bottom: $space-large;
}
a {
align-items: center;
cursor: pointer;
display: flex;
.account--name {
cursor: pointer;
font-size: $font-size-medium;
font-weight: $font-weight-medium;
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: $font-size-mini;
text-transform: capitalize;
}
}
}
.app-context-menu {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
height: 6rem;
}
.current-user--options {
font-size: $font-size-big;
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
}
.secondary-menu .nested.vertical.menu {
margin-left: var(--space-small);
}
</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,25 +1,31 @@
<template>
<div
v-if="showShowCurrentAccountContext"
class="account-context--group"
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="account-context--name text-ellipsis">
<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="account-context--switch-group">
<woot-button
variant="clear"
size="tiny"
icon="arrow-swap"
class="switch-button"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
<div
v-if="showSwitchButton"
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
variant="clear"
size="tiny"
icon="arrow-swap"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
</div>
</div>
</transition>
</div>
@@ -50,52 +56,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
.account-context--group {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
font-size: var(--font-size-mini);
padding: var(--space-small);
margin: var(--space-small) var(--space-small) 0 var(--space-small);
position: relative;
&:hover {
background: var(--b-100);
<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%
);
}
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
.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%
);
}
}
.switch-button {
margin: 0 var(--space-small);
}
.account-context--switch-group {
--overlay-shadow: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
align-items: center;
background-image: var(--overlay-shadow);
border-top-left-radius: 0;
border-top-right-radius: var(--border-radius-normal);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--border-radius-normal);
display: flex;
height: 100%;
justify-content: flex-end;
opacity: 1;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
@@ -103,6 +98,6 @@ export default {
.fade-enter,
.fade-leave-to {
opacity: 0;
@apply opacity-0;
}
</style>
@@ -2,31 +2,39 @@
<woot-modal
:show="showAccountModal"
:on-close="() => $emit('close-account-modal')"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="account-selector--wrap">
<div class="px-8 pt-4 pb-8">
<div
v-for="account in currentUser.accounts"
:id="`account-${account.id}`"
:key="account.id"
class="account-selector"
class="pt-0 pb-0"
>
<button
class="button expanded clear link"
class="flex justify-between items-center expanded clear link cursor-pointer px-4 py-3 w-full rounded-lg hover:underline hover:bg-slate-25 dark:hover:bg-slate-900"
@click="onChangeAccount(account.id)"
>
<span class="button__content">
<label :for="account.name" class="account-details--wrap">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
<span class="w-full">
<label :for="account.name" class="text-left rtl:text-right">
<div
class="text-slate-700 text-lg dark:text-slate-100 font-medium hover:underline-offset-4 leading-5"
>
{{ account.name }}
</div>
<div
class="text-slate-500 text-xs dark:text-slate-500 font-medium hover:underline-offset-4"
>
{{ account.role }}
</div>
</label>
</span>
<fluent-icon
v-show="account.id === accountId"
class="selected--account"
class="text-slate-800 dark:text-slate-100"
icon="checkmark-circle"
type="solid"
size="24"
@@ -74,32 +82,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.account-selector--wrap {
margin-top: var(--space-normal);
}
.account-selector {
padding-top: 0;
padding-bottom: 0;
.button {
display: flex;
justify-content: space-between;
padding: var(--space-one) var(--space-normal);
.account-details--wrap {
text-align: left;
.account--name {
cursor: pointer;
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: var(--font-size-mini);
text-transform: capitalize;
}
}
}
}
</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,5 +1,5 @@
<template>
<div class="logo">
<div class="w-8 h-8">
<router-link :to="dashboardPath" replace>
<img :src="source" :alt="name" />
</router-link>
@@ -30,17 +30,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
$logo-size: 32px;
.logo {
padding: var(--space-normal);
img {
width: $logo-size;
height: $logo-size;
object-fit: cover;
object-position: left center;
}
}
</style>
@@ -1,15 +1,25 @@
<template>
<div class="notifications-link">
<woot-button
class-names="notifications-link--button"
variant="clear"
color-scheme="secondary"
:class="{ 'is-active': isNotificationPanelActive }"
<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-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,
}"
@click="openNotificationPanel"
>
<fluent-icon icon="alert" />
<span v-if="unreadCount" class="badge warning">{{ unreadCount }}</span>
</woot-button>
<fluent-icon
icon="alert"
:class="{
'text-woot-500': isNotificationPanelActive,
}"
/>
<span
v-if="unreadCount"
class="text-black-900 bg-yellow-300 absolute -top-0.5 -right-1 p-1 text-xxs min-w-[1rem] rounded-full"
>
{{ unreadCount }}
</span>
</button>
</div>
</template>
<script>
@@ -43,37 +53,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.notifications-link {
margin-bottom: var(--space-small);
}
.badge {
position: absolute;
right: var(--space-minus-smaller);
top: var(--space-minus-smaller);
}
.notifications-link--button {
display: flex;
position: relative;
border-radius: var(--border-radius-large);
border: 1px solid transparent;
color: var(--s-600);
margin: var(--space-small) 0;
&:hover {
background: var(--w-50);
color: var(--s-600);
}
&:focus {
border-color: var(--w-500);
}
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
</style>
@@ -3,11 +3,10 @@
<div
v-if="show"
v-on-clickaway="onClickAway"
class="options-menu dropdown-pane"
:class="{ 'dropdown-pane--open': show }"
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-20 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 />
<li class="divider" />
<woot-dropdown-menu>
<woot-dropdown-item v-if="showChangeAccountOption">
<woot-button
@@ -50,7 +49,7 @@
>
<a
:href="href"
class="button small clear secondary"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
:class="{ 'is-active': isActive }"
@click="e => handleProfileSettingClick(e, navigate)"
>
@@ -61,10 +60,21 @@
</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"
class="button small clear secondary"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
target="_blank"
rel="noopener nofollow noreferrer"
@click="$emit('close')"
@@ -146,15 +156,10 @@ export default {
onClickAway() {
if (this.show) this.$emit('close');
},
openAppearanceOptions() {
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'appearance_settings' });
},
},
};
</script>
<style lang="scss" scoped>
.options-menu.dropdown-pane {
left: var(--space-slab);
bottom: var(--space-larger);
min-width: var(--space-giga);
top: unset;
z-index: var(--z-index-low);
}
</style>
@@ -1,11 +1,14 @@
<template>
<div class="primary--sidebar">
<logo
:source="logoSource"
:name="installationName"
:account-id="accountId"
/>
<nav class="menu vertical">
<div
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
:source="logoSource"
:name="installationName"
:account-id="accountId"
class="m-4 mb-10"
/>
<primary-nav-item
v-for="menuItem in menuItems"
:key="menuItem.toState"
@@ -14,8 +17,8 @@
:to="menuItem.toState"
:is-child-menu-active="menuItem.key === activeMenuItem"
/>
</nav>
<div class="menu vertical user-menu">
</div>
<div class="flex flex-col items-center justify-end pb-6">
<primary-nav-item
v-if="!isACustomBrandedInstance"
icon="book-open-globe"
@@ -101,27 +104,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.primary--sidebar {
display: flex;
flex-direction: column;
width: var(--space-jumbo);
border-right: 1px solid var(--s-50);
box-sizing: content-box;
height: 100%;
flex-shrink: 0;
}
.menu {
align-items: center;
margin-top: var(--space-medium);
}
.user-menu {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: flex-end;
margin-bottom: var(--space-normal);
}
</style>
@@ -3,15 +3,28 @@
<a
v-tooltip.right="$t(`SIDEBAR.${name}`)"
:href="href"
class="button clear button--only-icon menu-item"
:class="{ 'is-active': isActive || isChildMenuActive }"
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,
}"
:rel="openInNewPage ? 'noopener noreferrer nofollow' : undefined"
:target="openInNewPage ? '_blank' : undefined"
@click="navigate"
>
<fluent-icon :icon="icon" />
<span class="show-for-sr">{{ name }}</span>
<span v-if="count" class="badge warning">{{ count }}</span>
<fluent-icon
:icon="icon"
:class="{
'text-woot-500': isActive || isChildMenuActive,
}"
/>
<span class="sr-only">{{ name }}</span>
<span
v-if="count"
class="text-black-900 bg-yellow-500 absolute -top-1 -right-1"
>
{{ count }}
</span>
</a>
</router-link>
</template>
@@ -45,40 +58,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.button {
margin: var(--space-small) 0;
}
.menu-item {
display: inline-flex;
position: relative;
border-radius: var(--border-radius-large);
border: 1px solid transparent;
color: var(--s-600);
&:hover {
background: var(--w-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-500);
}
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
.icon {
font-size: var(--font-size-default);
}
.badge {
position: absolute;
right: var(--space-minus-smaller);
top: var(--space-minus-smaller);
}
</style>
@@ -1,7 +1,14 @@
<template>
<div v-if="hasSecondaryMenu" class="main-nav secondary-menu">
<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-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 name="menu-list" tag="ul" class="menu vertical">
<transition-group
name="menu-list"
tag="ul"
class="pt-2 list-none ml-0 mb-0"
>
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
:key="menuItem.toState"
@@ -249,27 +256,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/woot';
.secondary-menu {
display: flex;
flex-direction: column;
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100%;
width: 20rem;
flex-shrink: 0;
overflow-y: hidden;
position: unset;
&:hover {
overflow-y: hidden;
}
.menu {
padding: var(--space-small);
overflow-y: auto;
}
}
</style>
@@ -5,38 +5,69 @@
custom
active-class="active"
>
<li :class="{ active: isActive }">
<li
class="font-medium h-7 my-1 hover:bg-slate-25 hover:text-bg-50 flex items-center px-2 rounded-md dark:hover:bg-slate-800"
:class="{
'bg-woot-25 dark:bg-slate-800': isActive,
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
}"
@click="navigate"
>
<a
:href="href"
class="button clear menu-item text-truncate"
:class="{ 'is-active': isActive, 'text-truncate': shouldTruncate }"
@click="navigate"
class="inline-flex text-left max-w-full w-full items-center"
>
<span v-if="icon" class="badge--icon">
<fluent-icon class="inbox-icon" :icon="icon" size="12" />
<span
v-if="icon"
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-700 dark:text-slate-200"
:class="{
'text-woot-500 dark:text-woot-500': isActive,
}"
:icon="icon"
size="12"
/>
</span>
<span
v-if="labelColor"
class="badge--label"
class="inline-flex rounded-sm bg-slate-100 h-3 w-3.5 mr-1.5 rtl:mr-0 rtl:ml-1.5 border border-slate-50 dark:border-slate-900"
:style="{ backgroundColor: labelColor }"
/>
<span
:title="menuTitle"
class="menu-label button__content"
:class="{ 'text-truncate': shouldTruncate }"
<div
class="items-center flex overflow-hidden whitespace-nowrap text-ellipsis w-full justify-between"
>
{{ label }}
<span v-if="showChildCount" class="count-view">
<span
:title="menuTitle"
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,
}"
>
{{ label }}
</span>
<span
v-if="showChildCount"
class="bg-slate-50 dark:bg-slate-700 rounded text-xxs font-medium mx-1 py-0 px-1"
:class="
isCountZero
? `text-slate-300 dark:text-slate-700`
: `text-slate-700 dark:text-slate-50`
"
>
{{ childItemCount }}
</span>
</span>
<span v-if="count" class="badge" :class="{ secondary: !isActive }">
{{ count }}
</span>
<span v-if="warningIcon" class="badge--icon">
</div>
<span
v-if="warningIcon"
class="inline-flex rounded-sm mr-1 bg-slate-100"
>
<fluent-icon
v-tooltip.top-end="$t('SIDEBAR.FACEBOOK_REAUTHORIZE')"
class="inbox-icon"
class="text-xxs"
:icon="warningIcon"
size="12"
/>
@@ -72,10 +103,6 @@ export default {
type: String,
default: '',
},
count: {
type: String,
default: '',
},
showChildCount: {
type: Boolean,
default: false,
@@ -87,7 +114,12 @@ export default {
},
computed: {
showIcon() {
return { 'text-truncate': this.shouldTruncate };
return {
'overflow-hidden whitespace-nowrap text-ellipsis': this.shouldTruncate,
};
},
isCountZero() {
return this.childItemCount === 0;
},
menuTitle() {
return this.shouldTruncate ? this.label : '';
@@ -95,90 +127,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
$badge-size: var(--space-normal);
$label-badge-size: var(--space-slab);
.button {
margin: var(--space-small) 0;
}
.menu-item {
display: inline-flex;
color: var(--s-600);
font-weight: var(--font-weight-medium);
width: 100%;
height: var(--space-medium);
padding: var(--space-smaller) var(--space-smaller);
margin: var(--space-smaller) 0;
text-align: left;
line-height: 1.2;
&:hover {
background: var(--s-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-300);
}
&.is-active {
background: var(--w-25);
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-500);
}
}
.menu-label {
flex-grow: 1;
}
.inbox-icon {
font-size: var(--font-size-nano);
}
.badge--label,
.badge--icon {
display: inline-flex;
border-radius: var(--border-radius-small);
margin-right: var(--space-smaller);
background: var(--s-100);
}
.badge--icon {
align-items: center;
height: $badge-size;
justify-content: center;
min-width: $badge-size;
}
.badge--label {
height: $label-badge-size;
min-width: $label-badge-size;
margin-left: var(--space-smaller);
border: 1px solid var(--color-border-light);
}
.badge.secondary {
min-width: unset;
background: var(--s-75);
color: var(--s-600);
font-weight: var(--font-weight-bold);
}
.count-view {
background: var(--s-50);
border-radius: var(--border-radius-normal);
color: var(--s-600);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-bold);
margin-left: var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
line-height: var(--font-size-small);
}
</style>
@@ -1,46 +1,57 @@
<template>
<li v-show="isMenuItemVisible" class="sidebar-item">
<div v-if="hasSubMenu" class="secondary-menu--wrap">
<span class="secondary-menu--header fs-small">
<li v-show="isMenuItemVisible" class="mt-1">
<div v-if="hasSubMenu" class="flex justify-between">
<span
class="text-sm text-slate-700 dark:text-slate-200 font-semibold my-2 px-2 pt-1"
>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
</span>
<div v-if="menuItem.showNewButton" class="submenu-icons">
<div v-if="menuItem.showNewButton" class="flex items-center">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
class="submenu-icon"
class="p-0 ml-2"
@click="onClickOpen"
/>
</div>
</div>
<router-link
v-else
class="secondary-menu--title secondary-menu--link fs-small"
class="rounded-lg leading-4 font-medium flex items-center p-2 m-0 text-sm text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
:class="computedClass"
:to="menuItem && menuItem.toState"
>
<fluent-icon
:icon="menuItem.icon"
class="secondary-menu--icon"
class="min-w-[1rem] mr-1.5 rtl:mr-0 rtl:ml-1.5"
size="14"
/>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
<span v-if="showChildCount(menuItem.count)" class="count-view">
<span
v-if="showChildCount(menuItem.count)"
class="rounded-md text-xxs font-medium mx-1 py-0 px-1"
:class="{
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600': isActiveView,
'bg-slate-50 dark:bg-slate-700': !isActiveView,
}"
>
{{ `${menuItem.count}` }}
</span>
<span
v-if="menuItem.beta"
data-view-component="true"
label="Beta"
class="beta"
class="px-1 mx-1 inline-block font-medium leading-4 border border-green-400 text-green-500 rounded-lg text-xxs"
>
{{ $t('SIDEBAR.BETA') }}
</span>
</router-link>
<ul v-if="hasSubMenu" class="nested vertical menu">
<ul v-if="hasSubMenu" class="list-none ml-0 mb-0">
<secondary-child-nav-item
v-for="child in menuItem.children"
:key="child.id"
@@ -55,21 +66,21 @@
/>
<router-link
v-if="showItem(menuItem)"
v-slot="{ href, isActive, navigate }"
v-slot="{ href, navigate }"
:to="menuItem.toState"
custom
>
<li class="menu-item--new">
<a
:href="href"
class="button small link clear secondary"
:class="{ 'is-active': isActive }"
@click="e => newLinkClick(e, navigate)"
>
<fluent-icon icon="add" size="16" />
<span class="button__content">
<li class="pl-1">
<a :href="href">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
@click="e => newLinkClick(e, navigate)"
>
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
</span>
</woot-button>
</a>
</li>
</router-link>
@@ -108,6 +119,12 @@ export default {
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
globalConfig: 'globalConfig/get',
}),
isCountZero() {
return this.menuItem.count === 0;
},
isActiveView() {
return this.computedClass.includes('active-view');
},
hasSubMenu() {
return !!this.menuItem.children;
},
@@ -178,7 +195,7 @@ export default {
this.isUnattended ||
this.isCurrentRoute
) {
return 'is-active';
return 'bg-woot-25 dark:bg-slate-800 text-woot-500 dark:text-woot-500 hover:text-woot-500 dark:hover:text-woot-500 active-view';
}
if (this.hasSubMenu) {
if (
@@ -187,12 +204,12 @@ export default {
this.isIntegrationsSettings ||
this.isApplicationsSettings
) {
return 'is-active';
return 'bg-woot-25 dark:bg-slate-800 text-woot-500 dark:text-woot-500 hover:text-woot-500 dark:hover:text-woot-500 active-view';
}
return ' ';
return 'hover:text-slate-700 dark:hover:text-slate-100';
}
return '';
return 'hover:text-slate-700 dark:hover:text-slate-100';
},
},
methods: {
@@ -233,132 +250,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.sidebar-item {
margin: var(--space-smaller) 0 0;
}
.secondary-menu--wrap {
display: flex;
justify-content: space-between;
margin-top: var(--space-small);
}
.secondary-menu--header {
color: var(--s-700);
display: flex;
font-weight: var(--font-weight-bold);
line-height: var(--space-normal);
margin: var(--space-small) 0;
padding: 0 var(--space-small);
}
.secondary-menu--title {
color: var(--s-600);
display: flex;
font-weight: var(--font-weight-medium);
line-height: var(--space-normal);
margin: var(--space-small) 0;
padding: 0 var(--space-small);
}
.secondary-menu--link {
display: flex;
align-items: center;
margin: 0;
padding: var(--space-small);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-normal);
color: var(--s-700);
&:hover {
background: var(--s-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-300);
}
&.router-link-exact-active,
&.is-active {
background: var(--w-25);
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-600);
}
}
.secondary-menu--icon {
margin-right: var(--space-smaller);
min-width: var(--space-normal);
}
.sub-menu-link {
color: var(--s-600);
}
.wrap {
display: flex;
align-items: center;
}
.label-color--display {
border-radius: var(--space-smaller);
height: var(--space-normal);
margin-right: var(--space-small);
min-width: var(--space-normal);
width: var(--space-normal);
}
.inbox-icon {
position: relative;
top: -1px;
}
.sidebar-item .menu-item--new {
padding: var(--space-small) 0;
.button {
display: inline-flex;
color: var(--s-500);
}
}
.beta {
padding-right: var(--space-smaller) !important;
padding-left: var(--space-smaller) !important;
margin: 0 var(--space-smaller) !important;
display: inline-block;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
line-height: 14px;
border: 1px solid transparent;
border-radius: 2em;
color: var(--g-800);
border-color: var(--g-700);
}
.count-view {
background: var(--s-50);
border-radius: var(--border-radius-normal);
color: var(--s-600);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-bold);
margin: 0 var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
}
.submenu-icons {
display: flex;
align-items: center;
.submenu-icon {
padding: 0;
margin-left: var(--space-small);
}
}
</style>
@@ -4,7 +4,6 @@ import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n';
import WootModal from 'dashboard/components/Modal';
import WootModalHeader from 'dashboard/components/ModalHeader';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
@@ -38,9 +37,7 @@ describe('accountSelctor', () => {
},
],
};
const accountId = 1;
const globalConfig = { createNewAccountFromDashboard: false };
let store = null;
let actions = null;
let modules = null;
@@ -49,44 +46,46 @@ describe('accountSelctor', () => {
modules = {
auth: {
getters: {
getCurrentAccountId: () => accountId,
getCurrentAccountId: () => 1,
getCurrentUser: () => currentUser,
},
},
globalConfig: {
getters: {
'globalConfig/get': () => globalConfig,
'globalConfig/get': () => ({ createNewAccountFromDashboard: false }),
},
},
};
store = new Vuex.Store({
actions,
modules,
});
let store = new Vuex.Store({ actions, modules });
accountSelector = mount(AccountSelector, {
store,
localVue,
i18n: i18nConfig,
propsData: {
showAccountModal: true,
},
propsData: { showAccountModal: true },
stubs: { WootButton: { template: '<button />' } },
});
});
it('title and sub title exist', () => {
const headerComponent = accountSelector.findComponent(WootModalHeader);
const topBar = headerComponent.find('.page-top-bar');
const titleComponent = topBar.find('.page-sub-title');
expect(titleComponent.text()).toBe('Switch Account');
const subTitleComponent = topBar.find('p');
expect(subTitleComponent.text()).toBe(
'Select an account from the following list'
);
const title = headerComponent.findComponent({ ref: 'modalHeaderTitle' });
expect(title.text()).toBe('Switch Account');
const content = headerComponent.findComponent({
ref: 'modalHeaderContent',
});
expect(content.text()).toBe('Select an account from the following list');
});
it('first account item is checked', () => {
const accountFirstItem = accountSelector.find('.account-selector svg');
expect(accountFirstItem.exists()).toBe(true);
const selectedAccountCheckmark = accountSelector.find(
'#account-1 > button > svg'
);
expect(selectedAccountCheckmark.exists()).toBe(true);
const otherAccountCheckmark = accountSelector.find(
'#account-2 > button > svg'
);
expect(otherAccountCheckmark.exists()).toBe(true);
});
});
@@ -19,10 +19,7 @@ localVue.component('woot-dropdown-menu', WootDropdownMenu);
localVue.component('woot-dropdown-divider', WootDropdownDivider);
localVue.component('woot-dropdown-item', WootDropdownItem);
const i18nConfig = new VueI18n({
locale: 'en',
messages: i18n,
});
const i18nConfig = new VueI18n({ locale: 'en', messages: i18n });
describe('AvailabilityStatus', () => {
const currentAvailability = 'online';
@@ -48,15 +45,13 @@ describe('AvailabilityStatus', () => {
},
};
store = new Vuex.Store({
actions,
modules,
});
store = new Vuex.Store({ actions, modules });
availabilityStatus = mount(AvailabilityStatus, {
store,
localVue,
i18n: i18nConfig,
stubs: { WootSwitch: { template: '<button />' } },
});
});
@@ -3,7 +3,9 @@ import SidemenuIcon from '../SidemenuIcon';
describe('SidemenuIcon', () => {
test('matches snapshot', () => {
const wrapper = shallowMount(SidemenuIcon);
const wrapper = shallowMount(SidemenuIcon, {
stubs: { WootButton: { template: '<button><slot /></button>' } },
});
expect(wrapper.vm).toBeTruthy();
expect(wrapper.element).toMatchSnapshot();
});
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SidemenuIcon matches snapshot 1`] = `
<woot-button
class="toggle-sidebar"
<button
class="-ml-3 text-black-900 dark:text-slate-300"
color-scheme="secondary"
icon="list"
size="small"
@@ -52,8 +52,8 @@ export default {
<style lang="scss">
.announcement-popup {
max-width: 24rem;
min-width: 16rem;
max-width: 15rem;
min-width: 10rem;
display: flex;
position: absolute;
flex-direction: column;

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