Compare commits

...
Author SHA1 Message Date
Muhsin KelothandGitHub aef9f05b9d Merge branch 'develop' into CW-3013 2024-03-11 21:50:30 +05:30
Muhsin Keloth 8c922fd8df Update incoming_message_service_spec.rb 2024-03-11 21:50:16 +05:30
Vishnu NarayananandGitHub aaf70cf1cf feat: add push notification when SLA missed (#9078)
* feat: add push notification when SLA missed

* chore: sent notification only for inbox members

* feat: add conv particpants+admins to SLA notification list

* chore: add spec to ensure notification is created

* chore: refactor to multiple alerts for SLA conditions

* chore: add sla_policy as secondary_actor in notification
2024-03-11 21:49:41 +05:30
Muhsin Keloth ec283f6657 fix: specs 2024-03-11 20:17:55 +05:30
Muhsin Keloth 4c6954fefc chore: code cleanup 2024-03-11 16:15:23 +05:30
Muhsin KelothandGitHub 9ef93fda87 Merge branch 'develop' into CW-3013 2024-03-11 15:24:54 +05:30
0685e04aae feat: Add inbox view under feature flag on the sidebar (#9049)
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-03-07 18:27:39 -08:00
PranavandGitHub b6bf6bd414 docs: Update README.md, remove broken badge (#9080) 2024-03-07 22:27:29 +05:30
Shivam MishraandGitHub 9f70b93ff3 feat: Add information about email in the transcript (#9020) 2024-03-06 22:28:33 -08:00
PranavandGitHub 505ede2761 fix: Change the column identifier from string to text to avoid overflow (#9073)
Fixes: https://linear.app/chatwoot/issue/CW-3118/cannot-subscribe-to-notifications-on-microsoft-edge

Fix the issue with notifications in Microsoft Edge. The Edge push notification payload identifier has more than 255 characters. The API calls were failing due to this. This PR would fix the issue.
2024-03-07 11:13:01 +05:30
Chatwoot BotandGitHub 56bf543f7e chore: Update translations (#9052) 2024-03-06 23:30:32 +05:30
Muhsin KelothandGitHub 216de4d123 fix: Conversation assignment notification title (#9071) 2024-03-06 23:05:13 +05:30
Muhsin KelothandGitHub ef90b7a3d8 feat: Revamp the notification title and content (#8988) 2024-03-06 20:51:56 +05:30
Muhsin KelothandGitHub 58dd2633ba feat: Add contact helper (#8989) 2024-03-06 17:39:39 +05:30
Shivam MishraandGitHub c62c512ec4 fix: inline attachments not handled if tag was missing (#9068) 2024-03-06 17:15:29 +05:30
Matheus RodriguesandGitHub e086cb0d86 docs: added missing type text for messages (#9067)
- It adds the text type of message which is missing on the documentation
2024-03-06 16:30:51 +05:30
b4d37fa16b fix: Update help center styles, fix routing issues (#9061)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-03-04 09:57:14 -08:00
Sivin VargheseandGitHub a9d42e8c7e fix: Update the broken styles in help center insert modal (#9062) 2024-03-04 08:28:47 -08:00
Sojan JoseandGitHub 81060a72a4 feat: Improve reply suggestions using help center articles (#9026) 2024-03-03 20:25:19 -08:00
881d4bf644 feat: Add backend APIs for the bot metrics (#9031)
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-03-01 08:20:20 -08:00
Sivin VargheseandGitHub 9581264286 fix: Broken styles (#9046) 2024-03-01 13:06:33 +05:30
Shivam MishraandGitHub 7c5c88b17a fix: Update border radius for login/unauthenticated pages (#9042) 2024-02-29 10:34:13 -08:00
Sivin VargheseandGitHub 351cc940ce fix: Fix the button variant success color in dark mode (#9043) 2024-02-29 10:33:31 -08:00
Sivin VargheseandGitHub 9c2147aced fix: UI breakages (#9040)
- Fixes overflow of contact details in the contact side panel.
- Fixes line height issue in article header.
- Fixes overflow of category names in the article list.
- Fixes typing indicator alignment issues
2024-02-29 15:10:29 +05:30
Sojan JoseandGitHub cb8f6f1a39 fix: Update the reply time tooltip to avoid confusion (#9037) 2024-02-28 12:22:09 -08:00
a92639e391 fix: Filter dropdown alignment issue in reports page (#9035)
* fix: Filter dropdown alignment issue in reports page

* fix: add hide class

* chore: Clean up

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-02-28 20:26:23 +05:30
dafedddc1a feat: Remove Foundation in favor of Tailwind (#8984)
* feat: Remove foundation

* chore: Minor fix

* Minor fix

* Update _forms.scss

* chore: More changes

* chore: Minor fix

* chore: Clean up

* fix: font-weight

* chore: More changes

* chore: Setting page

* chore: Editor fix

* chore: Reports page

* chore: More changes

* chore: Minor changes

* chore: More fixes

* chore: More changes

* chore: More changes

* chore: More changes

* chore: Minor fix

* chore: More changes

* chore: More changes

* chore: More changes

* chore: More changes

* chore: Clean up

* chore: Minor fix

* chore: Clean ups

* chore: Rename basic file

* chore: Remove unused files

* chore: Fix expanded input

* Fix campaign rendering

* chore: Clean up

* chore: More changes

* chore: Remove unused files

* fix: Overflow issue

* chore: Minor fix

* chore: Clean up

* chore: Minor fix

* chore: Remove unused files

* chore: Minor fix

* chore: Minor fix

* fix: autoprefixer start/end value has mixed support

* chore: Minor fix

* chore: Remove unused files

* chore: Minor fix

* chore: Minor fix

* chore: Minor fix

* Add responsive design to label settings

* fix inbox view

* chore: Minor fix

* w-60% to w-2/3

* chore: Fix team

* chore: Fix button

* w-[34%] to w-1/3

* chore: Fix border

* Add support mobile views in team page

* chore: fix snackbar

* chore: clean up

* chore: Clean up

* fix: loading state alignment

* fix: alert styles

* chore: Minor fix

* fix: spacing for agent bot row

* fix: layout

* fix: layout for SLA

* fix: checkbox

* fix: SLA checkbox spacing

* Update inbox settings pages

* fix macros listing page layout

* fix canned responses

* chore: Fix bot page

* chore: fix automation page

* chore: fix agents page

* chore: fix canned response editor

* chore: Fix settings table

* chore: fix settings layout

* chore: Minor fix

* fix: canned response table layou

* fix: layout for table header for webhooks

* fix: webhook row layout

* fix: dashboard app modal layout

* fix: add title to canned response truncated shortcode

* fix: dashboard apps row layuot

* fix: layouts hooks

* fix: body color

* fix: delete action color in portal locales

* fix: text color for campagin title

* fix: success button color

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-02-28 13:56:28 +05:30
9f905ce2e6 feat: Update the input for the SLA threshold selection (#8974)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-27 23:00:24 -08:00
PranavandGitHub dca14ef82d fix: Downgrade rack-cors to 2.0.0 to fix CVE-2024-27456 (#9032) 2024-02-27 20:20:59 -08:00
ac249c75c4 chore: Update the metric card component to support generic cases (#9030)
Rename the CSAT metric card to a generic name, updated the implementation to use composition API and removed all the custom CSS in the component to conform with TailwindCSS styles
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-27 17:51:06 -08:00
Sojan JoseandGitHub 41e269e873 feat(ee): Add reporting events for bots (#9027)
Added a new event conversation_bot_resolved and added a job to auto resolve the bot conversations if there was no activity for the last 1 hour.
2024-02-27 14:53:28 -08:00
b7a83dcbcd feat: Add a placeholder for image attachments in conversations view (#8969)
We analyze an image to get it's height and width. On the frontend, we would show a placeholder with the corresponding width and height until the images are loaded properly.

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-02-27 14:46:16 -08:00
Danilo Campana FuchsandGitHub fd993feece chore(docs): Update the API documentation to add the filter status=all to conversation list and meta (#8994) 2024-02-26 15:15:55 -08:00
773be6f8ec feat: Interface to validate response_source (#8894)
- This PR adds a UI to validate the response source quality quickly. It also helps to test with sample questions and update responses in the database when missing.

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2024-02-26 20:20:12 +05:30
Vishnu NarayananandGitHub 77e463990a feat: seed db for heroku review apps (#9019) 2024-02-26 16:41:05 +05:30
Vishnu NarayananandGitHub 8f6ff4bc41 chore: refactor deploy check gh action (#9018)
* chore: refactor deploy check action

    Refactor deployment check gh action to account for the initial setup time required for heroku review apps
    Process /api response via jq only if http.status_code is 200
2024-02-26 16:14:01 +05:30
Sojan JoseandGitHub ab56374b2f chore: Fix Bundle Audit Breakages (#9016)
Name: actionpack
Version: 7.0.8
CVE: CVE-2024-26143
Criticality: Unknown
URL: https://discuss.rubyonrails.org/t/possible-xss-vulnerability-in-action-controller/84947

-----

Name: rack
Version: 2.2.8
CVE: CVE-2024-25126
Criticality: Unknown
URL: https://discuss.rubyonrails.org/t/denial-of-service-vulnerability-in-rack-content-type-parsing/84941
2024-02-26 15:25:21 +05:30
Shivam MishraandGitHub fa305d76e8 fix: Add custom rubocop rules for email fetch (#8987) 2024-02-23 11:58:02 -08:00
Vishnu NarayananandGitHub 1de3ff1a6d chore(infra): Add GH action to ensure deployment success for every PR (#8990) 2024-02-22 15:28:28 -08:00
Shivam MishraandGitHub 106319406b fix: remove rubocop rule (#8985)
* fix: remove rubocop rule

* chore: remove ignore comment

* chore: remove ignore comment
2024-02-22 13:36:15 +05:30
Chatwoot BotandGitHub a5e9cd94e1 chore: Update translations (#8983) 2024-02-21 16:01:14 -08:00
27ac262a26 feat(ee): Add support for SLA in automation rules (#8910)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-02-21 14:45:43 -08:00
ae4c8d818f feat: Ability to block contacts permanently (#8922)
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-21 14:18:42 -08:00
Shivam MishraandGitHub c031cb19d2 fix: downcase email before finding (#8921)
* fix: downcase email when finding

* feat: add `from_email` class

* refactor: use `from_email`

* feat: add rule to disallow find_by email directly

* chore:  remove redundant test

Since the previous imlpmentation didn't do a case-insentive search, a new user would be created, and the error would be raised at the DB layer. With the new changes, this test case is redundant

* refactor: use from_email
2024-02-21 18:51:00 +05:30
ebae547a60 feat: Add ability to resolve API channel conversations (#8348)
- Create a new endpoint to fetch a single conversation in client apis
- Create a new endpoint to resolve a single conversation in client apis
- Update swagger API definition to include missing endpoints

Fixes: #6329

Co-authored-by: Cristian Duta <Cristian.Duta@ti8m.ch>
2024-02-21 17:41:20 +05:30
cc47ccaa2c feat(ee): Add SLA management UI (#8777)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-20 23:03:22 -08:00
Vishnu NarayananandGitHub d53097f77d fix: Raise error if email to_header is invalid (#8688) 2024-02-20 17:03:39 -08:00
9911c5dc12 chore: Hide banners on onboarding view (#8934)
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-02-20 16:12:51 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
19aef3e94b chore(deps): bump ip from 1.1.5 to 1.1.9 (#8976)
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.5 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.5...v1.1.9)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-20 16:11:02 -08:00
23230e0143 feat: sla-7 ensure applied_sla uniqueness (#8938)
* feat: refactor fetching sla in action service

* chore: modify spec

* chore: ensure applied_sla uniqueness

* chore: review fixes

* feat: add unique index on applied_sla

* chore: fix spec

* chore: add new specs to improve coverage

* chore: improve spec

* Update spec/enterprise/services/enterprise/action_service_spec.rb

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-02-20 21:59:49 +05:30
Vishnu NarayananandGitHub f92cea144c feat(perf): sla-9 improve perf of TriggerSlasForAccountsJob (#8953)
* feat: improve perf of TriggerSlasForAccountsJob
2024-02-20 21:54:37 +05:30
Muhsin KelothandGitHub e6cf8c39b7 feat: Update last_snoozed_at after the un-snooze notification (#8943) 2024-02-20 18:55:39 +05:30
Muhsin KelothandGitHub c5c0845151 feat: Add labels, status and priority in notification push_event_data (#8972) 2024-02-20 17:17:25 +05:30
5d9fb55370 feat: Export contact improvements (#8895)
This pull request enhances the export contacts feature by adding a confirmation step before exporting. Previously, clicking the export button would trigger the export action without confirmation.

Additionally, it ensures that only the intended recipient receives the export email, addressing the previous behaviour where all administrators received it.

Fixes: #8504

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-02-20 17:11:03 +05:30
721a2f5052 feat: API changes to support multi step user signup (#8933)
-API Changes to support the new onboarding flow 

Co-authored-by: Sojan <sojan@pepalo.com>
2024-02-20 16:48:51 +05:30
7320957405 chore: Add facebook_api_version to global config (#8965)
- Ability to configure facebook_api_version from global config

Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-20 15:53:29 +05:30
Sojan e4d072c79c Merge branch 'release/3.6.0' into develop 2024-02-19 15:59:26 +05:30
Sojan 71ee10c889 Bump version to 3.6.0 2024-02-19 15:58:21 +05:30
Sojan JoseandGitHub 978a8a4cb2 fix: support_email and inbound_email_domain returning empty string (#8963)
chore: Fix for inbound email domain being nil
2024-02-19 15:43:35 +05:30
cd06b2b337 fix: Inbox view Read/Snoozed display filters (#8907)
* fix: Notification filters

* Update notification_finder.rb

* Update notification_finder.rb

* Update notification_finder.rb

* fix: spec

* fix: specs

* Update notification_finder.rb

* fix: add more fixes

* Update notification_finder.rb

* fix: specs

* chore: better comments

* chore: removed filtering

* chore: refactoring

* fix: review fixes

* fix: API call

* chore: Minor fix

* Rename spec

* Fix params getting undefined

* Fix finder

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-17 13:59:25 +05:30
6eb06377cc feat: Add textarea component to use in forms (#8932)
Co-authored-by: Pranav <pranav@chatwoot.com>
2024-02-16 12:19:08 -08:00
Sojan JoseandGitHub 94892e7168 chore: Fix flaky spec relying on unguaranteed order (#8956) 2024-02-16 12:11:39 -08:00
Muhsin KelothandGitHub fe744abe26 fix: Mark notification as unread after the un-snooze (#8917) 2024-02-16 17:30:54 +05:30
Nithin David ThomasandGitHub a902b49bc5 feat: Adds bulk_invite api for onboarding view (#8931)
- New API for bulk email invite
2024-02-16 17:01:27 +05:30
Sojan JoseandGitHub 9c07b6dd46 chore: Support for updated_within in ConverationFinder (#8947)
- `updated_within' accepts value in seconds and returns all conversations updated in the given period with out pagination. This API will assist in our refetch logic on socket disconnect

ref: #8888
2024-02-16 16:44:55 +05:30
Muhsin KelothandGitHub 3b7b06dbec feat: Add meta to notification model (#8941)
* feat: Add meta and description to notification model

* chore: remove description
2024-02-16 14:38:02 +05:30
Sivin VargheseandGitHub 597a4164e8 fix: Editor hyperlink popup is hidden under modal (#8950) 2024-02-16 08:19:06 +05:30
Nithin David ThomasandGitHub 678c00f254 fix: Removes default slot for right of label (#8936) 2024-02-14 10:48:54 -08:00
Nithin David ThomasandGitHub 7298002da7 fix: Adds forgot password link for login form (#8927)
- focus ring not appearing issue
- missing forgot password link

Fixes: https://linear.app/chatwoot/issue/CW-3090/fix-fixes-issues-with-login-form
2024-02-14 16:31:32 +05:30
Vishnu NarayananandGitHub 96b781b7fd fix: throttle contact search endpoint to prevent abuse (#8919)
Throttle contact_search endpoint to prevent abuse/maintain db perf
ref: https://discord.com/channels/897869226579222540/899920096972111883/1206919316402999326
2024-02-13 18:48:11 +05:30
Pranav Raj SandGitHub 1ce5cbe275 feat: Add endpoints to retrieve summary of team/agents over a period of time (#8916)
- Internal APIs to prototype reporting improvements.
2024-02-13 15:44:40 +05:30
Pranav Raj SandGitHub c607f09be0 feat: Improve the rendering of CSV exports (#8914)
Improve the rendering of the datestamp
2024-02-12 20:01:58 -08:00
Shivam MishraandGitHub 33a6ad9f7e chore: add more logging to mailbox helpers [CW-3071] (#8909)
* chore: add more logging to mailbox helpers

* fix: deleted entries

* fix: log order

* refactor: log using `processed_mail.message_id`
2024-02-13 08:51:50 +05:30
Shivam MishraandGitHub e662336eaf feat: handle already invited user (#8912) 2024-02-13 08:51:42 +05:30
Shivam MishraandGitHub 0158a55845 fix: Update outline color on login page (#8913) 2024-02-12 11:03:09 -08:00
657843960c feat: account onboarding with clearbit (#8857)
* feat: add clearbit lookup

* chore: fix typo in .env.example

* refactor: split lookup to reduce cognitive complexity

* feat: add more fields to lookup

* feat: extend accounts controller

* feat: save extra data to custom_attributes

* feat: allow v2 update with custom_attributes

* feat: add update route

* refactor: reduce complexity

* feat: move update to v1 controller

* test: add locale test

* feat: remove update from routes

* test: update API for custom attributes

* test: all custom attributes

* fix: v2 tests

* test: enterprise accounts controller

* fix: clearbit payload

* fix: with modified env

* feat: allow custom attributes updates to profile

* refactor: reduce complexity

* feat: allow clearbit api key in installation config

* refactor: move clearbit to internal

* feat: allow clearbit

* chore: add display_title for June

* feat: allow more internal options

* refactor: use globalconfig to fetch clearbit token

* test: move response body to a factory

* refactor: update ops

* chore: remove clearbit from .env.example

* chore: apply suggestions from code review

Co-authored-by: sojan-official <sojan@chatwoot.com>

---------

Co-authored-by: sojan-official <sojan@chatwoot.com>
2024-02-12 23:21:42 +05:30
Sai WarangandGitHub fc6a22b072 fix:Avoid adding all new conversations when on a custom view (#8905) 2024-02-12 07:10:53 -08:00
Muhsin KelothandGitHub bca3d98268 Merge branch 'develop' into CW-3013 2024-02-12 16:21:48 +05:30
227d99934e feat: Agent bot cant assign conversations to teams (#8015)
Implemented so that the API can process priority and agent/team changes per Agent Bot.

Fixes:  #7474

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-02-12 15:51:22 +05:30
Vishnu NarayananandGitHub 5036b28e45 chore: fix git_sha in instance health (#8893)
- Fix git-hash in super admin instance health page for Heroku installations
- Fix git boundary not set error for docker/heroku deployments which remove .git directory
2024-02-12 14:33:15 +05:30
Muhsin KelothandGitHub 95cd68813f Merge branch 'develop' into CW-3013 2024-02-12 14:32:30 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8b5735c915 chore(deps): bump grpc from 1.54.0 to 1.54.3 (#8906)
Bumps [grpc](https://github.com/google/grpc) from 1.54.0 to 1.54.3.
- [Release notes](https://github.com/google/grpc/releases)
- [Changelog](https://github.com/grpc/grpc/blob/master/doc/grpc_release_schedule.md)
- [Commits](https://github.com/google/grpc/compare/v1.54.0...v1.54.3)

---
updated-dependencies:
- dependency-name: grpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 12:52:45 +05:30
Pranav Raj SandGitHub eb379e1849 fix(refactor): Cleanup the specs and the logic for FetchIMAP job (#8766) 2024-02-10 14:03:50 -08:00
Pranav Raj SandGitHub 6a630bc489 chore: Render timestamp in a better format in conversation traffic export (#8897)
chore: Render timestamp in a better format in conversation traffic reports
2024-02-09 00:48:10 -08:00
Vishnu NarayananandGitHub 0d1b474e60 chore: upgrade to latest versions of gh helper actions (#8896)
https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/

Fixes: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2024-02-09 10:56:36 +04:00
Sojan JoseandGitHub 84f6cadae7 chore: Remove unused builders for widget (#8876)
- These builders are not used in our code base, hence removing them
2024-02-08 21:12:55 +04:00
Sojan JoseandGitHub 372e740c38 chore: Add CODEOWNERS (#8889) 2024-02-08 07:44:08 -08:00
Muhsin Keloth ac85ccf2cf Merge branch 'CW-3013' of https://github.com/chatwoot/chatwoot into CW-3013 2024-02-08 16:24:35 +05:30
Muhsin Keloth 78a4dce5b9 fix: Contact create logic 2024-02-08 16:22:13 +05:30
Muhsin KelothandGitHub 28c1dd0a26 Merge branch 'develop' into CW-3013 2024-02-08 15:47:56 +05:30
Muhsin Keloth 377aeb8087 chore: fix specs 2024-02-08 15:47:31 +05:30
57dd979a14 feat: Inbox list filter (#8880)
* feat: Inbox list filter

* fix: routes after delete/unread

* fix: Specs

* feat: Handle sort in frontend

* chore: Minor fixes

* chore: Minor fix

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-08 12:11:01 +05:30
Muhsin KelothandGitHub 117a1405e5 Merge branch 'develop' into CW-3013 2024-02-08 11:25:41 +05:30
c1d07a5471 feat: Add sidekiq jobs to monitor applied SLAs (#8828)
Fixes: https://linear.app/chatwoot/issue/CW-2983/sidekiq-jobservice-to-monitor-sla-breach

Co-authored-by: Sojan <sojan@pepalo.com>
2024-02-07 21:44:56 +04:00
98eddd0532 fix: Send push notification on bot_handoff (#7636)
Send a 'Conversation Created' push notification when the bot does a hand_off.
fixes: #7587

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-07 17:45:08 +04:00
Muhsin KelothandGitHub 1b21e0d429 feat: Add notification.updated event (#8871) 2024-02-07 18:15:51 +05:30
Muhsin Keloth 1f8ac8a59c Merge branch 'CW-3013' of https://github.com/chatwoot/chatwoot into CW-3013 2024-02-07 17:32:41 +05:30
Muhsin Keloth 78855afe8b Update incoming_message_service_spec.rb 2024-02-07 17:32:39 +05:30
Muhsin Keloth 2566155e04 Update incoming_message_service_spec.rb 2024-02-07 16:20:51 +05:30
Muhsin Keloth ce6ae2cc47 Merge branch 'CW-3013' of https://github.com/chatwoot/chatwoot into CW-3013 2024-02-07 15:57:55 +05:30
Muhsin Keloth 9ecb847d9b feat: set first and last name for email 2024-02-07 15:57:44 +05:30
Muhsin KelothandGitHub fdffc39663 Merge branch 'develop' into CW-3013 2024-02-07 15:50:20 +05:30
Muhsin Keloth ac5aee3c8d Update incoming_message_whatsapp_cloud_service_spec.rb 2024-02-07 15:50:08 +05:30
Sojan JoseandGitHub 7776b74126 chore: Apply fixes for items in rubocop_todo [CW-1806] (#8864)
This PR addresses several items listed in our rubocop_todo by implementing the necessary corrections and enhancements. As a result, we are now able to remove the rubocop_todo file entirely, streamlining our codebase and ensuring adherence to our coding standards.

fixes: https://linear.app/chatwoot/issue/CW-1806/chore-rubocop-audit
2024-02-07 13:36:04 +04:00
Muhsin KelothandGitHub 94ede8a654 Merge branch 'develop' into CW-3013 2024-02-07 14:42:32 +05:30
Muhsin KelothandGitHub 3abb887445 fix: Change the route from inbox to inbox-view (#8874)
* fix: Change inbox to inbox-view

* fix: route fixes

* Update ContactInfo.vue

* Update conversation.routes.js
2024-02-07 14:34:18 +05:30
Muhsin Keloth 07a7c1eb8b fix: specs 2024-02-07 14:34:11 +05:30
Muhsin Keloth b125ac1ad6 Update message_builder.rb 2024-02-07 14:05:32 +05:30
Muhsin Keloth ecfa19bb1a fix: route push fixes 2024-02-07 14:02:29 +05:30
Muhsin KelothandGitHub 733a851607 Merge branch 'develop' into CW-3013 2024-02-07 13:35:57 +05:30
a9ec3ae098 test: ensure that timestamp is in correct range (#8868)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-07 13:35:31 +05:30
Muhsin Keloth 8c4b13b737 feat: set first and last name for WhatsApp 2024-02-07 13:33:57 +05:30
Muhsin Keloth 1c73aa9ef4 feat: set first and last name for instagram 2024-02-07 13:20:41 +05:30
53927b5a72 chore: Inbox view improvements (#8866)
* chore: Inbox view improvements

* chore: Review fix

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-07 13:14:58 +05:30
Muhsin Keloth bafaf9c786 feat: add split_first_and_last_name helper 2024-02-07 12:42:53 +05:30
Muhsin KelothandGitHub 75dd77a92d Merge branch 'develop' into CW-3013 2024-02-07 12:38:11 +05:30
Muhsin KelothandGitHub d67b91d2b0 feat: Sort Notification API changes (#8865)
* feat: Inbox sort API changes

* Update notification_finder.rb

* Update notification_finder.rb

* Update notification_finder.rb
2024-02-06 21:03:22 +05:30
Sojan JoseandGitHub a4fc28abc0 chore: Squash migrations till 05/2023 (#8862)
- Squash the old migrations up 05/2023 to a single file
2024-02-06 17:00:38 +04:00
bee2a14620 feat: Creates radio select form input for onboarding form (#8860)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-02-06 03:21:46 -08:00
168a4dc323 fix: build error during development using docker-compose (#8714)
* Fix ssl error in final build stage

* add env to docker compose

---------

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2024-02-06 12:56:53 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
47c0decf1e chore(deps): bump nokogiri from 1.16.0 to 1.16.2 (#8861)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.0 to 1.16.2.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.0...v1.16.2)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-06 09:47:16 +04:00
8acc818f68 feat: allow bulk invite create via email (#8853)
* feat: add agent builder

* feat: use new agent builder

* refactor: validate limit

* test: agent limits

* feat: allow bulk create

* feat: allow bulk create

* refactor: rename current_user to inviter in AgentBuilder

* refactor: move limits tests to enterprise

* test: send correct params

* refactor: account builder returns both user and account_user

* chore: Revert "refactor: account builder returns both user and account_user"

This reverts commit 1419789871e8a3b8ff57af27fe53925b1486a839.

* feat: return user as is

* Update agent_builder.rb

- minor update

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-02-06 09:04:04 +05:30
9e0468cd73 feat: Inbox header actions (Snooze/Delete) (#8858)
* feat: Inbox header actions (Snooze/Delete)

* chore: Minor fix

* chore: Fix eslint

* Update inboxHotKeys.js

* feat: custom snooze

* Update actions.spec.js

* chore: Clean up

* chore: add snoozed_until to notification end point

* chore: Minor fix

* chore: Minor style fix

* chore:Clean up

* chore: review fixes

* chore: Minor fix

* chore: Adds alert

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-06 08:54:15 +05:30
Nithin David ThomasandGitHub 65e9cee019 feat: Form input component to use with onboarding form (#8850)
* feat: Form input component to use with onboarding form

* Update Input.vue

* Update WithLabel.vue

* Review fixes
2024-02-05 10:17:06 -08:00
bb3851a35c feat: Wizard step component to use with onboarding (#8854)
* feat: Wizard step component to use with onboarding

* Update app/javascript/v3/views/onboarding/OnboardingStep.vue

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>

* Update app/javascript/v3/views/onboarding/OnboardingStep.vue

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>

* Update app/javascript/v3/views/onboarding/OnboardingStep.vue

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>

* Review fixes

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-02-05 09:34:33 -08:00
Nithin David ThomasandGitHub 4368bdb2bc feat: Form select component to use with onboarding form (#8852)
* feat: Form select component to use with onboarding form

* Update Select.vue

* Update WithLabel.vue
2024-02-05 09:19:44 -08:00
Sojan JoseandGitHub 1b753720c1 chore: Move Enterprise pricing validation to on create (#8856)
* chore: Move Pricing validation to on create

The previous validation was getting triggered on all transactions,
this would prevent login to existing users and session data need to be updated.
Opting for a less intrusive approach.

* chore: minore change
2024-02-05 19:18:29 +05:30
Muhsin KelothandGitHub 39e27d2a23 feat: Delete all/read notifications (#8844) 2024-02-05 13:33:05 +05:30
Sivin VargheseandGitHub 45e630fc60 feat: Inbox list display menu (#8847)
* feat: Inbox list display menu

* chore: Fix alignment
2024-02-04 10:23:16 +05:30
0c35a77d4b feat: Show a confirmation banner if the email is not verified (#8808)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2024-02-02 12:31:29 -08:00
07ea9694a3 feat: new accounts controller for signup+onboarding (#8804)
* feat: add v2 accounts controller

* feat: allow empty account and user name

* feat: ensure  and  is present for v1 signup

* test: remove validation checks

* chore: apply suggestions

* chore: revert en.yml formatting

* chore: line at EOF

* fix: routes

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-02 16:10:45 +05:30
Sivin VargheseandGitHub 85043e7d88 feat: Inbox page view (#8841) 2024-02-02 12:45:07 +05:30
d3c1fce761 feat: Inbox item actions (#8838)
* feat: Inbox item actions

* feat: add inbox id in push event data

* Update InboxList.vue

* feat: complete actions

* Update InboxList.vue

* Update InboxView.vue

* chore: code cleanup

* chore: fix specs

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-02-02 11:58:47 +05:30
33e98bf61a fix: Use BODY.PEEK[HEADER] to avoid parsing issues with mail providers (#8833)
Co-authored-by: Sojan <sojan@pepalo.com>
2024-02-01 17:24:59 -08:00
Nithin David ThomasandGitHub cae7cb7002 fix: Fixes overlapping issue with local selector dropdown in portal header (#8839) 2024-02-01 03:46:55 -08:00
b8047f0912 feat: sla-2 add automation backend support for SLA (#8775)
* feat: add automation support for SLA

* feat: add sla action in automtion UI

* chore: revert frontend changes

* chore: refactor to ee namespace

* chore: refactor automation rule to ee namespace

* feat: create applied_sla table entry

* chore: add applied_sla spec

* chore: rubocop fixes

---------

Co-authored-by: Sojan <sojan@pepalo.com>
2024-02-01 15:42:12 +05:30
PabloandGitHub de98e434d6 chore: Update year in LICENSE (#8835)
Update year in LICENSE
2024-02-01 15:37:05 +05:30
Sivin VargheseandGitHub 74e5e2163a feat: Inbox options dropdown menu (#8836) 2024-02-01 15:17:24 +05:30
9464d4d647 feat: Inbox card context menu component (#8815)
* feat: Inbox item context menu component

* chore: Minor fix

* chore: Minor height fix

* fix: Conflict

* minor fix

* chore: Fix conflicts

* chore:  Minor fix

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2024-02-01 14:56:15 +05:30
b7a7e5a0d3 feat: Inbox list API integration (#8825)
* feat: Inbox view

* feat: Bind real values

* chore: code cleanup

* feat: add observer

* fix: Inbox icon

* chore: more code cleanup

* chore: Replace conversation id

* chore: Minor fix

* chore: Hide from side bar

* chore: Fix eslint

* chore: Minor fix

* fix: dark mode color

* chore: Minor fix

* feat: Add description for each notification types

* chore: remove commented code

* Update InboxList.vue

* Update InboxView.vue

* chore: fix specs

* fix: specs

* Update InboxView.vue

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2024-02-01 12:10:58 +05:30
Sivin VargheseandGitHub b9c62b3fed feat: Inbox list header (#8831)
* feat: Inbox list header

* fix: Border
2024-02-01 10:22:09 +05:30
Sojan JoseandGitHub d10525a714 fix: Template sync issue for older Whatsapp channels (#8830)
- This PR introduces a modification to the channel fetching logic, ensuring that channels with older message_template_last_updated timestamps are prioritized during synchronization.
2024-01-31 18:38:01 +04:00
Sojan JoseandGitHub 390cd756e8 chore: Reorganize the installation config settings (#8794)
- Reorganizing installation config settings to move more configurations into UI from environment variables
- Changes to installation config to support premium plans in the enterprise edition
- Fixes the broken premium indicator in account/show and accounts/edit page
2024-01-31 16:48:42 +04:00
Pranav Raj SandGitHub ee3f734b7b chore: Refactor RefreshOauthTokenService to improve readability (#8820)
- Added a trait called microsoft_email for the Channel::Email factory.
- Rewrote the logic to make it simple to understand
- Rewrote the specs for readability
2024-01-31 12:24:12 +04:00
Pranav Raj SandGitHub 905ca94f71 chore: Remove unused attribute imap_inbox_synced_at (#8822)
- imap_inbox_synced_at is no longer used; this PR removes all the references to the same.
2024-01-31 11:48:46 +04:00
lkfdjasklandGitHub 53d42b15b8 fix: Avoid oneOf usage in Open API 2.0 spec (#8819)
Small fixes to make spec adhere to swagger 2.0
2024-01-30 16:09:18 -08:00
Vishnu NarayananandGitHub 17cb788193 fix: Upgrade gmail_xoauth gem to 0.4.3 (#8817) 2024-01-30 11:11:38 -08:00
Muhsin KelothandGitHub f2115b15f7 feat: Add inbox view page (#8814)
* feat: Add inbox view page

* Update accounts.js

* Update index.js
2024-01-30 13:55:20 +05:30
Sojan JoseandGitHub 0805f362d3 chore: Validation for purchased pricing_plan_quantity (#8809)
- Add validations based on purchased pricing_plan_quantity
- Modify the Dashboard to support the new error message
2024-01-30 11:26:07 +04:00
Sivin VargheseandGitHub eeb0113dc5 feat: Inbox item header component (#8810) 2024-01-30 11:38:33 +05:30
648c4caca1 chore: Move EE OpenAI spec to correct folder (#8805)
- We previously had this spec inside the enterprise folder which wouldn't be picked up by our build process, so moving to the correct folder instead.

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-01-29 20:09:17 +04:00
Muhsin KelothandGitHub ffbc7cb846 Merge branch 'develop' into CW-3013 2024-01-29 21:18:27 +05:30
Muhsin Keloth d88d195e73 fix: specs 2024-01-29 21:06:08 +05:30
Shivam MishraandGitHub 2eeec22868 fix: Cookies.set does not stringify JSON (#8807)
* fix: cookie setting

* chore: remove debug statement

* chore: add specs to test stringify
2024-01-29 18:14:49 +05:30
Muhsin KelothandGitHub 27c51a0bdf Merge branch 'develop' into CW-3013 2024-01-29 17:11:52 +05:30
Muhsin Keloth 822411d804 feat: Store first name and last name for the supporting channels 2024-01-29 17:10:37 +05:30
Muhsin KelothandGitHub ef50edb9e2 feat: Standardise the external channel user id and user name (#8802)
* feat: Standardize the external channel id and user name

* chore: add specs

* chore: add name space `social`
2024-01-29 16:57:10 +05:30
Shivam MishraandGitHub 3ed80fa867 feat: expiry for cache keys [CW-3038] (#8793)
* feat: set cache keys for 3 days only

* feat: invalidate should set the latest timestamp

* refactor: cache_keys concern

* remove invalidate_cache method
* refactor reset to set to new value instead of delete
* ensure only one event is dispatched

* feat: set expiry to 24 hours

* chore: make expiry 48 hours

* feat: include destroy event

* feat: set expiry to 72 days

* fix: typo

* test: cache update after `touch`

* test: update cache keys

* refactor: remove after_touch, it's already handled in commit
2024-01-29 15:27:26 +05:30
Sivin VargheseandGitHub cf664ca2a0 feat: Inbox item card component (#8801) 2024-01-29 14:52:55 +05:30
Muhsin KelothandGitHub 485c561b18 feat: Add attributes location and country_code to Contact model (#8803)
feat: Add attributes location and country_code to Contact model
2024-01-29 14:10:14 +05:30
Pranav Raj SandGitHub 766698cb3a chore: Upgrade @june-so/analytics-next, js-cookie to the latest version (#8799)
This is a small change, upgrading 2 packages to the latest version. getJSON is removed from the latest version, I've added a patch for the same across the codebase.

fixes: https://linear.app/chatwoot/issue/CW-3035/upgrade-dependencies
2024-01-29 11:41:42 +04:00
Shivam MishraandGitHub 082793290a feat: allow multiple files for FB and Insta [CW-3019] (#8783)
* feat: allow multiple messages

* fix: typo

* feat: send content and attachments both

* refactor: message sequence

* test: multiple attachments for instagram

* test: multiple attachments on facebook
2024-01-28 13:20:54 +05:30
1423 changed files with 23851 additions and 9114 deletions
-1
View File
@@ -254,7 +254,6 @@ AZURE_APP_SECRET=
# Sentiment analysis model file path
SENTIMENT_FILE_PATH=
# Housekeeping/Performance related configurations
# Set to true if you want to remove stale contact inboxes
# contact_inboxes with no conversation older than 90 days will be removed
+7
View File
@@ -0,0 +1,7 @@
## All javascript files should be reviewed by pranav before merging
*.js @pranavrajs
*.vue @pranavrajs
## All enterprise related files should be reviewed by sojan before merging
/enterprise/* @sojan-official
+45
View File
@@ -0,0 +1,45 @@
## github action to check deployment success
## curl the deployment url and check for 200 status
## deployment url will be of the form chatwoot-pr-<pr_number>.herokuapp.com
name: Deploy Check
on:
pull_request:
jobs:
deployment_check:
name: Check Deployment
runs-on: ubuntu-latest
steps:
- name: Install jq
run: sudo apt-get install -y jq
- name: Print Deployment URL
run: echo "https://chatwoot-pr-${{ github.event.pull_request.number }}.herokuapp.com"
- name: Check Deployment Status
run: |
max_attempts=10
attempt=1
status_code=0
echo "Waiting for review app to be deployed/redeployed, trying in 10 minutes..."
sleep 600
while [ $attempt -le $max_attempts ]; do
response=$(curl -s -o /dev/null -w "%{http_code}" https://chatwoot-pr-${{ github.event.pull_request.number }}.herokuapp.com/api)
status_code=$(echo $response | head -n 1)
if [ $status_code -eq 200 ]; then
body=$(curl -s https://chatwoot-pr-${{ github.event.pull_request.number }}.herokuapp.com/api)
if echo "$body" | jq -e '.version and .timestamp and .queue_services == "ok" and .data_services == "ok"' > /dev/null; then
echo "Deployment successful"
exit 0
else
echo "Deployment status unknown, retrying in 3 minutes..."
sleep 180
fi
else
echo "Waiting for review app to be ready, retrying in 3 minutes..."
sleep 180
attempt=$((attempt + 1))
fi
done
echo "Deployment failed after $max_attempts attempts"
exit 1
fi
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Check for log lines and calculate percentage
run: |
+3 -3
View File
@@ -4,7 +4,7 @@
# # This action will try to install and setup
# # chatwoot on an Ubuntu 20.04 machine using
# # the linux installer script.
# #
# #
# # This is set to run daily at midnight.
# #
@@ -35,7 +35,7 @@ jobs:
run: |
sudo ./install.sh --install < input
# disabling http verify for now as http
# disabling http verify for now as http
# access to port 3000 fails in gh action env
# - name: Verify
# if: always()
@@ -45,7 +45,7 @@ jobs:
# curl http://localhost:3000/api
- name: Upload chatwoot setup log file as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: chatwoot-setup-log-file
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
GIT_REF: ${{ github.head_ref || github.ref_name }} # ref_name to get tags/branches
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
+4 -4
View File
@@ -41,7 +41,7 @@ jobs:
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -50,7 +50,7 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
@@ -76,11 +76,11 @@ jobs:
- name: Run backend tests
run: |
bundle exec rspec --profile=10 --format documentation
env:
env:
NODE_OPTIONS: --openssl-legacy-provider
- name: Upload rails log folder
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: rails-log-folder
+4 -3
View File
@@ -40,7 +40,7 @@ jobs:
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -49,7 +49,7 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
@@ -73,11 +73,12 @@ jobs:
spec/enterprise/controllers/api/v1/accounts/response_sources_controller_spec.rb \
spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb \
spec/enterprise/controllers/enterprise/api/v1/accounts/inboxes_controller_spec.rb:47 \
spec/enterprise/jobs/enterprise/account/conversations_resolution_scheduler_job_spec.rb \
--profile=10 \
--format documentation
- name: Upload rails log folder
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: rails-log-folder
+3 -3
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -19,7 +19,7 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
@@ -31,7 +31,7 @@ jobs:
run: |
rm -rf enterprise
rm -rf spec/enterprise
- name: Run asset compile
run: bundle exec rake assets:precompile
env:
+24 -70
View File
@@ -2,7 +2,8 @@ require:
- rubocop-performance
- rubocop-rails
- rubocop-rspec
inherit_from: .rubocop_todo.yml
- ./rubocop/use_from_email.rb
- ./rubocop/custom_cop_location.rb
Layout/LineLength:
Max: 150
@@ -12,7 +13,8 @@ Metrics/ClassLength:
Exclude:
- 'app/models/message.rb'
- 'app/models/conversation.rb'
Metrics/MethodLength:
Max: 19
RSpec/ExampleLength:
Max: 25
Style/Documentation:
@@ -50,6 +52,7 @@ Lint/OrAssignmentToConstant:
Exclude:
- 'lib/redis/config.rb'
Metrics/BlockLength:
Max: 30
Exclude:
- spec/**/*
- '**/routes.rb'
@@ -59,6 +62,7 @@ Metrics/ModuleLength:
Exclude:
- lib/seeders/message_seeder.rb
- spec/support/slack_stubs.rb
- app/mailboxes/mailbox_helper.rb
Rails/ApplicationController:
Exclude:
- 'app/controllers/api/v1/widget/messages_controller.rb'
@@ -102,84 +106,31 @@ RSpec/FactoryBot/SyntaxMethods:
Enabled: false
Naming/VariableNumber:
Enabled: false
Metrics/MethodLength:
Naming/MemoizedInstanceVariableName:
Exclude:
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
- 'db/migrate/20211219031453_update_foreign_keys_on_delete.rb'
Rails/CreateTableWithTimestamps:
Exclude:
- 'db/migrate/20170207092002_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb'
- 'app/models/message.rb'
Style/GuardClause:
Exclude:
- 'app/builders/account_builder.rb'
- 'app/models/attachment.rb'
- 'app/models/message.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Metrics/AbcSize:
Max: 26
Exclude:
- 'app/controllers/concerns/auth_helper.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
- 'app/controllers/api/v1/accounts/inboxes_controller.rb'
- 'db/migrate/20211219031453_update_foreign_keys_on_delete.rb'
Metrics/CyclomaticComplexity:
Max: 7
Exclude:
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161110102609_removeinboxid.rb'
- 'db/migrate/20170519091539_add_avatar_to_fb.rb'
- 'db/migrate/20191020085608_rename_old_tables.rb'
- 'db/migrate/20191126185833_update_user_invite_foreign_key.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
- 'db/migrate/20210513083044_remove_not_null_from_webhook_url_channel_api.rb'
Rails/BulkChangeTable:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20200121190901_create_account_users.rb'
- 'db/migrate/20170211092540_notnullableusers.rb'
- 'db/migrate/20170403095203_contactadder.rb'
- 'db/migrate/20170406104018_add_default_status_conv.rb'
- 'db/migrate/20170511134418_latlong.rb'
- 'db/migrate/20191027054756_create_contact_inboxes.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
- 'db/migrate/20210425093724_convert_integration_hook_settings_field.rb'
Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/channel/twitter_profile.rb'
- 'app/models/webhook.rb'
- 'app/models/contact.rb'
- 'app/models/integrations/hook.rb'
- 'app/models/canned_response.rb'
- 'app/models/telegram_bot.rb'
Rails/RenderInline:
Exclude:
- 'app/controllers/swagger_controller.rb'
Performance/CollectionLiteralInLoop:
Exclude:
- 'db/migrate/20210315101919_enable_email_channel.rb'
Rails/ThreeStateBooleanColumn:
Exclude:
- 'db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb'
- 'db/migrate/20200605130625_agent_away_message_to_auto_reply.rb'
- 'db/migrate/20200606132552_create_labels.rb'
- 'db/migrate/20201027135006_create_working_hours.rb'
- 'db/migrate/20210112174124_add_hmac_token_to_inbox.rb'
- 'db/migrate/20210114202310_create_teams.rb'
- 'db/migrate/20210212154240_add_request_for_email_on_channel_web_widget.rb'
- 'db/migrate/20210428135041_add_campaigns.rb'
- 'db/migrate/20210602182058_add_hmac_to_api_channel.rb'
- 'db/migrate/20210609133433_add_email_collect_to_inboxes.rb'
- 'db/migrate/20210618095823_add_csat_toggle_for_inbox.rb'
- 'db/migrate/20210927062350_add_trigger_only_during_business_hours_collect_to_campaigns.rb'
- 'db/migrate/20211027073553_add_imap_smtp_config_to_channel_email.rb'
- 'db/migrate/20211109143122_add_tweet_enabled_flag_to_twitter_channel.rb'
- 'db/migrate/20211216110209_add_allow_messages_after_resolved_to_inbox.rb'
- 'db/migrate/20220116103902_add_open_ssl_verify_mode_to_channel_email.rb'
- 'db/migrate/20220216151613_add_open_all_day_to_working_hour.rb'
- 'db/migrate/20220511072655_add_archive_column_to_portal.rb'
- 'db/migrate/20230503101201_create_sla_policies.rb'
RSpec/IndexedLet:
Enabled: false
@@ -187,9 +138,21 @@ RSpec/NamedSubject:
Enabled: false
# we should bring this down
RSpec/MultipleExpectations:
Max: 7
RSpec/MultipleMemoizedHelpers:
Max: 14
# custom rules
UseFromEmail:
Enabled: true
Exclude:
- 'app/models/user.rb'
- 'app/models/contact.rb'
CustomCopLocation:
Enabled: true
AllCops:
NewCops: enable
Exclude:
@@ -203,13 +166,4 @@ AllCops:
- 'config/environments/**/*'
- 'tmp/**/*'
- 'storage/**/*'
- 'db/migrate/20200225162150_init_schema.rb'
- 'db/migrate/20210611180222_create_active_storage_variant_records.active_storage.rb'
- 'db/migrate/20210611180221_add_service_name_to_active_storage_blobs.active_storage.rb'
- db/migrate/20200309213132_add_account_id_to_agent_bot_inboxes.rb
- db/migrate/20200331095710_add_identifier_to_contact.rb
- db/migrate/20200429082655_add_medium_to_twilio_sms.rb
- db/migrate/20200503151130_add_account_feature_flag.rb
- db/migrate/20200927135222_add_last_activity_at_to_conversation.rb
- db/migrate/20210306170117_add_last_activity_at_to_contacts.rb
- db/migrate/20220809104508_revert_cascading_indexes.rb
- 'db/migrate/20230426130150_init_schema.rb'
-287
View File
@@ -1,287 +0,0 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-10-23 16:47:02 +0530 using RuboCop version 0.73.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
Lint/DuplicateMethods:
Exclude:
- 'app/controllers/api/v1/reports_controller.rb'
# Offense count: 1
Lint/RescueException:
Exclude:
- 'app/builders/messages/message_builder.rb'
# Offense count: 4
Lint/ShadowingOuterLocalVariable:
Exclude:
- 'app/controllers/api/v1/reports_controller.rb'
# Offense count: 3
# Configuration parameters: AllowKeywordBlockArguments.
Lint/UnderscorePrefixedVariableName:
Exclude:
- 'app/models/account.rb'
- 'deploy/before_symlink.rb'
# Offense count: 18
Lint/UselessAssignment:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/facebook_indicators_controller.rb'
- 'app/listeners/action_cable_listener.rb'
- 'app/listeners/reporting_listener.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/facebook_page.rb'
# Offense count: 14
Metrics/AbcSize:
Max: 26
# Offense count: 1
# Configuration parameters: CountComments, ExcludedMethods.
# ExcludedMethods: refine
Metrics/BlockLength:
Max: 30
# Offense count: 2
Metrics/CyclomaticComplexity:
Max: 7
# Offense count: 10
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/MethodLength:
Max: 19
# Offense count: 1
Metrics/PerceivedComplexity:
Max: 8
# Offense count: 6
Naming/AccessorMethodName:
Exclude:
- 'app/builders/report_builder.rb'
- 'app/controllers/api/v1/accounts_controller.rb'
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/conversations_controller.rb'
# Offense count: 9
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
Exclude:
- 'app/controllers/api/base_controller.rb'
- 'app/controllers/api/v1/conversations_controller.rb'
- 'app/controllers/api/v1/webhooks_controller.rb'
- 'app/controllers/application_controller.rb'
- 'app/models/message.rb'
- 'lib/integrations/widget/outgoing_message_builder.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: MaxKeyValuePairs.
Performance/RedundantMerge:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/models/message.rb'
# Offense count: 1
# Cop supports --auto-correct.
Performance/StringReplacement:
Exclude:
- 'lib/events/base.rb'
# Offense count: 4
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
RSpec/ContextWording:
Exclude:
- 'spec/models/contact_spec.rb'
- 'spec/models/user_spec.rb'
# Offense count: 1
RSpec/DescribeClass:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
RSpec/DescribeSymbol:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AllowConsecutiveOneLiners.
RSpec/EmptyLineAfterExample:
Exclude:
- 'spec/models/user_spec.rb'
# Offense count: 1
# Configuration parameters: Max.
RSpec/ExampleLength:
Exclude:
- 'spec/models/conversation_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_line_only, single_statement_only, disallow
RSpec/ImplicitSubject:
Exclude:
- 'spec/models/user_spec.rb'
# Offense count: 7
# Configuration parameters: AggregateFailuresByDefault.
RSpec/MultipleExpectations:
Max: 7
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: not_to, to_not
RSpec/NotToNot:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Exclude:
- 'spec/models/conversation_spec.rb'
# Offense count: 4
# Cop supports --auto-correct.
Rails/ActiveRecordAliases:
Exclude:
- 'app/controllers/api/v1/agents_controller.rb'
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/canned_responses_controller.rb'
- 'app/controllers/api/v1/contacts_controller.rb'
# Offense count: 2
# Cop supports --auto-correct.
Rails/BelongsTo:
Exclude:
- 'app/models/message.rb'
- 'app/models/user.rb'
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/EnumHash:
Exclude:
- 'app/models/attachment.rb'
- 'app/models/conversation.rb'
- 'app/models/message.rb'
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasManyOrHasOneDependent:
Exclude:
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/InverseOf:
Exclude:
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb
Rails/LexicallyScopedActionFilter:
Exclude:
- 'app/controllers/home_controller.rb'
# Offense count: 2
# Configuration parameters: Include.
# Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb
Rails/Output:
Exclude:
- 'app/bot/bot.rb'
- 'app/builders/account_builder.rb'
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: strict, flexible
Rails/TimeZone:
Exclude:
- 'app/builders/report_builder.rb'
- 'lib/reports/update_account_identity.rb'
- 'lib/reports/update_agent_identity.rb'
- 'lib/reports/update_identity.rb'
- 'spec/models/conversation_spec.rb'
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/Validation:
Exclude:
- 'app/models/canned_response.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/facebook_page.rb'
- 'app/models/telegram_bot.rb'
- 'app/models/user.rb'
# Offense count: 15
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/builders/messages/message_builder.rb'
- 'app/controllers/api/v1/inbox_members_controller.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/channel/web_widget.rb'
- 'app/presenters/conversations/event_data_presenter.rb'
- 'app/services/facebook/send_reply_service.rb'
- 'lib/integrations/facebook/delivery_status.rb'
- 'lib/integrations/facebook/message_creator.rb'
- 'lib/integrations/facebook/message_parser.rb'
- 'lib/integrations/widget/incoming_message_builder.rb'
# Offense count: 4
Style/CommentedKeyword:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/conversations/assignments_controller.rb'
- 'app/controllers/api/v1/conversations/labels_controller.rb'
- 'app/controllers/api/v1/labels_controller.rb'
# Offense count: 1
# Configuration parameters: AllowIfModifier.
Style/IfInsideElse:
Exclude:
- 'app/finders/conversation_finder.rb'
# Offense count: 1
Style/MixinUsage:
Exclude:
- 'app/bot/bot.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'spec/**/*'
- 'app/controllers/api/v1/callbacks_controller.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, explicit
Style/RescueStandardError:
Exclude:
- 'app/models/channel/facebook_page.rb'
+2 -2
View File
@@ -3,8 +3,8 @@ source 'https://rubygems.org'
ruby '3.2.2'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~> 7.0.8.0'
gem 'rack-cors', '2.0.0', require: 'rack/cors'
gem 'rails', '~> 7.0.8.1'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false
+72 -72
View File
@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
actioncable (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
actionmailbox (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8)
actionpack (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activesupport (= 7.0.8)
actionmailer (7.0.8.1)
actionpack (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8)
actionview (= 7.0.8)
activesupport (= 7.0.8)
actionpack (7.0.8.1)
actionview (= 7.0.8.1)
activesupport (= 7.0.8.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.8)
actionpack (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
actiontext (7.0.8.1)
actionpack (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8)
activesupport (= 7.0.8)
actionview (7.0.8.1)
activesupport (= 7.0.8.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.8)
activesupport (= 7.0.8)
activejob (7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.3.6)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activemodel (7.0.8.1)
activesupport (= 7.0.8.1)
activerecord (7.0.8.1)
activemodel (= 7.0.8.1)
activesupport (= 7.0.8.1)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activesupport (= 7.0.8)
activestorage (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activesupport (= 7.0.8.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8)
activesupport (7.0.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -277,7 +277,7 @@ GEM
gli (2.21.1)
globalid (1.2.1)
activesupport (>= 6.1)
gmail_xoauth (0.4.2)
gmail_xoauth (0.4.3)
oauth (>= 0.3.6)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
@@ -316,16 +316,16 @@ GEM
google-cloud-translate-v3 (0.6.0)
gapic-common (>= 0.17.1, < 2.a)
google-cloud-errors (~> 1.0)
google-protobuf (3.22.3)
google-protobuf (3.22.3-arm64-darwin)
google-protobuf (3.22.3-x86_64-darwin)
google-protobuf (3.22.3-x86_64-linux)
google-protobuf (3.25.2)
google-protobuf (3.25.2-arm64-darwin)
google-protobuf (3.25.2-x86_64-darwin)
google-protobuf (3.25.2-x86_64-linux)
googleapis-common-protos (1.4.0)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
grpc (~> 1.27)
googleapis-common-protos-types (1.6.0)
google-protobuf (~> 3.14)
googleapis-common-protos-types (1.11.0)
google-protobuf (~> 3.18)
googleauth (1.5.2)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
@@ -335,13 +335,13 @@ GEM
signet (>= 0.16, < 2.a)
groupdate (6.2.1)
activesupport (>= 5.2)
grpc (1.54.0)
grpc (1.54.3)
google-protobuf (~> 3.21)
googleapis-common-protos-types (~> 1.0)
grpc (1.54.0-x86_64-darwin)
grpc (1.54.3-x86_64-darwin)
google-protobuf (~> 3.21)
googleapis-common-protos-types (~> 1.0)
grpc (1.54.0-x86_64-linux)
grpc (1.54.3-x86_64-linux)
google-protobuf (~> 3.21)
googleapis-common-protos-types (~> 1.0)
haikunator (1.1.1)
@@ -488,14 +488,14 @@ GEM
newrelic_rpm (9.6.0)
base64
nio4r (2.7.0)
nokogiri (1.16.0)
nokogiri (1.16.2)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.0-arm64-darwin)
nokogiri (1.16.2-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.0-x86_64-darwin)
nokogiri (1.16.2-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.0-x86_64-linux)
nokogiri (1.16.2-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
@@ -559,12 +559,12 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.3)
rack (2.2.8)
rack (2.2.8.1)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-contrib (2.4.0)
rack (< 4)
rack-cors (2.0.1)
rack-cors (2.0.0)
rack (>= 2.0.0)
rack-mini-profiler (3.2.0)
rack (>= 1.2.0)
@@ -575,20 +575,20 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.8)
actioncable (= 7.0.8)
actionmailbox (= 7.0.8)
actionmailer (= 7.0.8)
actionpack (= 7.0.8)
actiontext (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activemodel (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
rails (7.0.8.1)
actioncable (= 7.0.8.1)
actionmailbox (= 7.0.8.1)
actionmailer (= 7.0.8.1)
actionpack (= 7.0.8.1)
actiontext (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activemodel (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
bundler (>= 1.15.0)
railties (= 7.0.8)
railties (= 7.0.8.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
@@ -596,9 +596,9 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
railties (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -794,7 +794,7 @@ GEM
valid_email2 (4.0.6)
activemodel (>= 3.2)
mail (~> 2.5)
version_gem (1.1.2)
version_gem (1.1.3)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -918,10 +918,10 @@ DEPENDENCIES
puma
pundit
rack-attack (>= 6.7.0)
rack-cors
rack-cors (= 2.0.0)
rack-mini-profiler (>= 3.2.0)
rack-timeout
rails (~> 7.0.8.0)
rails (~> 7.0.8.1)
redis
redis-namespace
responders (>= 3.1.1)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2017-2021 Chatwoot Inc.
Copyright (c) 2017-2024 Chatwoot Inc.
Portions of this software are licensed as follows:
-1
View File
@@ -23,7 +23,6 @@ Customer engagement suite, an open-source alternative to Intercom, Zendesk, Sale
<img src="https://img.shields.io/github/commit-activity/m/chatwoot/chatwoot" alt="Commits-per-month">
<a title="Crowdin" target="_self" href="https://chatwoot.crowdin.com/chatwoot"><img src="https://badges.crowdin.net/e/37ced7eba411064bd792feb3b7a28b16/localized.svg"></a>
<a href="https://discord.gg/cJXdrwS"><img src="https://img.shields.io/discord/647412545203994635" alt="Discord"></a>
<a href="https://huntr.dev/bounties/disclose"><img src="https://cdn.huntr.dev/huntr_security_badge_mono.svg" alt="Huntr"></a>
<a href="https://status.chatwoot.com"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fchatwoot%2Fstatus%2Fmaster%2Fapi%2Fchatwoot%2Fuptime.json" alt="uptime"></a>
<a href="https://status.chatwoot.com"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fchatwoot%2Fstatus%2Fmaster%2Fapi%2Fchatwoot%2Fresponse-time.json" alt="response time"></a>
<a href="https://artifacthub.io/packages/helm/chatwoot/chatwoot"><img src="https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/artifact-hub" alt="Artifact HUB"></a>
+5
View File
@@ -72,6 +72,11 @@
"scripts": {
"test": "bundle exec rake test"
}
},
"review": {
"scripts": {
"postdeploy": "bundle exec rails db:seed"
}
}
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ class ContactIdentifyAction
def existing_email_contact
return if params[:email].blank?
@existing_email_contact ||= account.contacts.find_by(email: params[:email])
@existing_email_contact ||= account.contacts.from_email(params[:email])
end
def existing_phone_number_contact
@@ -9,7 +9,7 @@
padding: 4px 12px;
.icon-container {
margin-right: 4px;
margin-right: 2px;
}
+14 -4
View File
@@ -2,7 +2,7 @@
class AccountBuilder
include CustomExceptions::Account
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale]
pattr_initialize [:account_name, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale]
def perform
if @user.nil?
@@ -15,12 +15,22 @@ class AccountBuilder
end
[@user, @account]
rescue StandardError => e
puts e.inspect
Rails.logger.debug e.inspect
raise e
end
private
def user_full_name
# the empty string ensures that not-null constraint is not violated
@user_full_name || ''
end
def account_name
# the empty string ensures that not-null constraint is not violated
@account_name || ''
end
def validate_email
address = ValidEmail2::Address.new(@email)
if address.valid? # && !address.disposable?
@@ -39,7 +49,7 @@ class AccountBuilder
end
def create_account
@account = Account.create!(name: @account_name, locale: I18n.locale)
@account = Account.create!(name: account_name, locale: I18n.locale)
Current.account = @account
end
@@ -64,7 +74,7 @@ class AccountBuilder
@user = User.new(email: @email,
password: user_password,
password_confirmation: user_password,
name: @user_full_name)
name: user_full_name)
@user.type = 'SuperAdmin' if @super_admin
@user.confirm if @confirmed
@user.save!
+60
View File
@@ -0,0 +1,60 @@
# The AgentBuilder class is responsible for creating a new agent.
# It initializes with necessary attributes and provides a perform method
# to create a user and account user in a transaction.
class AgentBuilder
# Initializes an AgentBuilder with necessary attributes.
# @param email [String] the email of the user.
# @param name [String] the name of the user.
# @param role [String] the role of the user, defaults to 'agent' if not provided.
# @param inviter [User] the user who is inviting the agent (Current.user in most cases).
# @param availability [String] the availability status of the user, defaults to 'offline' if not provided.
# @param auto_offline [Boolean] the auto offline status of the user.
pattr_initialize [:email, { name: '' }, :inviter, :account, { role: :agent }, { availability: :offline }, { auto_offline: false }]
# Creates a user and account user in a transaction.
# @return [User] the created user.
def perform
ActiveRecord::Base.transaction do
@user = find_or_create_user
send_confirmation_if_required
create_account_user
end
@user
end
private
# Finds a user by email or creates a new one with a temporary password.
# @return [User] the found or created user.
def find_or_create_user
user = User.from_email(email)
return user if user
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password)
end
# Sends confirmation instructions if the user is persisted and not confirmed.
def send_confirmation_if_required
@user.send_confirmation_instructions if user_needs_confirmation?
end
# Checks if the user needs confirmation.
# @return [Boolean] true if the user is persisted and not confirmed, false otherwise.
def user_needs_confirmation?
@user.persisted? && !@user.confirmed?
end
# Creates an account user linking the user to the current account.
def create_account_user
AccountUser.create!({
account_id: account.id,
user_id: @user.id,
inviter_id: inviter.id
}.merge({
role: role,
availability: availability,
auto_offline: auto_offline
}.compact))
end
end
@@ -3,6 +3,7 @@
# for contact inbox logic it uses the contact inbox builder
class ContactInboxWithContactBuilder
include ContactHelper
pattr_initialize [:inbox!, :contact_attributes!, :source_id, :hmac_verified]
def perform
@@ -49,8 +50,12 @@ class ContactInboxWithContactBuilder
end
def create_contact
# TODO: Consider name as first_name and we will change the name to full_name in the future
name, middle_name, last_name = extract_name_parts
account.contacts.create!(
name: contact_attributes[:name] || ::Haikunator.haikunate(1000),
name: name,
middle_name: middle_name,
last_name: last_name,
phone_number: contact_attributes[:phone_number],
email: contact_attributes[:email],
identifier: contact_attributes[:identifier],
@@ -59,6 +64,20 @@ class ContactInboxWithContactBuilder
)
end
def extract_name_parts
# Return a generated name if name attribute is blank
return [::Haikunator.haikunate(1000), '', ''] if contact_attributes[:name].blank?
# Return the all name parts if middle_name or last_name is present
if contact_attributes[:middle_name].present? || contact_attributes[:last_name].present?
return [contact_attributes[:name], contact_attributes[:middle_name], contact_attributes[:last_name]]
end
# If name is present, split it into first and last name
name_parts = parse_name(contact_attributes[:name])
[name_parts[:first_name], name_parts[:middle_name], name_parts[:last_name]]
end
def find_contact
contact = find_contact_by_identifier(contact_attributes[:identifier])
contact ||= find_contact_by_email(contact_attributes[:email])
@@ -75,7 +94,7 @@ class ContactInboxWithContactBuilder
def find_contact_by_email(email)
return if email.blank?
account.contacts.find_by(email: email.downcase)
account.contacts.from_email(email)
end
def find_contact_by_phone_number(phone_number)
@@ -104,7 +104,8 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
def process_contact_params_result(result)
{
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
name: result['first_name'] || 'John',
last_name: result['last_name'] || 'Doe',
account_id: @inbox.account_id,
avatar_url: result['profile_pic']
}
+10
View File
@@ -54,6 +54,13 @@ class V2::ReportBuilder
}
end
def bot_summary
{
bot_resolutions_count: bot_resolutions.count,
bot_handoffs_count: bot_handoffs.count
}
end
def conversation_metrics
if params[:type].equal?(:account)
live_conversations
@@ -71,6 +78,8 @@ class V2::ReportBuilder
avg_first_response_time
avg_resolution_time reply_time
resolutions_count
bot_resolutions_count
bot_handoffs_count
reply_time].include?(params[:metric])
end
@@ -123,6 +132,7 @@ class V2::ReportBuilder
unattended: @open_conversations.unattended.count
}
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
metric[:pending] = @open_conversations.pending.count if params[:type].equal?(:account)
metric
end
end
@@ -0,0 +1,53 @@
class V2::Reports::AgentSummaryBuilder < V2::Reports::BaseSummaryBuilder
pattr_initialize [:account!, :params!]
def build
set_grouped_conversations_count
set_grouped_avg_reply_time
set_grouped_avg_first_response_time
set_grouped_avg_resolution_time
prepare_report
end
private
def set_grouped_conversations_count
@grouped_conversations_count = Current.account.conversations.where(created_at: range).group('assignee_id').count
end
def set_grouped_avg_resolution_time
@grouped_avg_resolution_time = get_grouped_average(reporting_events.where(name: 'conversation_resolved'))
end
def set_grouped_avg_first_response_time
@grouped_avg_first_response_time = get_grouped_average(reporting_events.where(name: 'first_response'))
end
def set_grouped_avg_reply_time
@grouped_avg_reply_time = get_grouped_average(reporting_events.where(name: 'reply_time'))
end
def group_by_key
:user_id
end
def reporting_events
@reporting_events ||= Current.account.reporting_events.where(created_at: range)
end
def prepare_report
account.account_users.each_with_object([]) do |account_user, arr|
arr << {
id: account_user.user_id,
conversations_count: @grouped_conversations_count[account_user.user_id],
avg_resolution_time: @grouped_avg_resolution_time[account_user.user_id],
avg_first_response_time: @grouped_avg_first_response_time[account_user.user_id],
avg_reply_time: @grouped_avg_reply_time[account_user.user_id]
}
end
end
def average_value_key
ActiveModel::Type::Boolean.new.cast(params[:business_hours]).present? ? :value_in_business_hours : :value
end
end
@@ -0,0 +1,17 @@
class V2::Reports::BaseSummaryBuilder
include DateRangeHelper
private
def group_by_key
# Override this method
end
def get_grouped_average(events)
events.group(group_by_key).average(average_value_key)
end
def average_value_key
params[:business_hours].present? ? :value_in_business_hours : :value
end
end
@@ -0,0 +1,54 @@
class V2::Reports::BotMetricsBuilder
include DateRangeHelper
attr_reader :account, :params
def initialize(account, params)
@account = account
@params = params
end
def metrics
{
conversation_count: bot_conversations.count,
message_count: bot_messages.count,
resolution_rate: bot_resolution_rate.to_i,
handoff_rate: bot_handoff_rate.to_i
}
end
private
def bot_activated_inbox_ids
@bot_activated_inbox_ids ||= account.inboxes.filter(&:active_bot?).map(&:id)
end
def bot_conversations
@bot_conversations ||= account.conversations.where(inbox_id: bot_activated_inbox_ids).where(created_at: range)
end
def bot_messages
@bot_messages ||= account.messages.outgoing.where(conversation_id: bot_conversations.ids).where(created_at: range)
end
def bot_resolutions_count
account.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_resolved,
created_at: range).distinct.count
end
def bot_handoffs_count
account.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_handoff,
created_at: range).distinct.count
end
def bot_resolution_rate
return 0 if bot_conversations.count.zero?
bot_resolutions_count.to_f / bot_conversations.count * 100
end
def bot_handoff_rate
return 0 if bot_conversations.count.zero?
bot_handoffs_count.to_f / bot_conversations.count * 100
end
end
@@ -0,0 +1,49 @@
class V2::Reports::TeamSummaryBuilder < V2::Reports::BaseSummaryBuilder
pattr_initialize [:account!, :params!]
def build
set_grouped_conversations_count
set_grouped_avg_reply_time
set_grouped_avg_first_response_time
set_grouped_avg_resolution_time
prepare_report
end
private
def set_grouped_conversations_count
@grouped_conversations_count = Current.account.conversations.where(created_at: range).group('team_id').count
end
def set_grouped_avg_resolution_time
@grouped_avg_resolution_time = get_grouped_average(reporting_events.where(name: 'conversation_resolved'))
end
def set_grouped_avg_first_response_time
@grouped_avg_first_response_time = get_grouped_average(reporting_events.where(name: 'first_response'))
end
def set_grouped_avg_reply_time
@grouped_avg_reply_time = get_grouped_average(reporting_events.where(name: 'reply_time'))
end
def reporting_events
@reporting_events ||= Current.account.reporting_events.where(created_at: range).joins(:conversation)
end
def group_by_key
'conversations.team_id'
end
def prepare_report
account.teams.each_with_object([]) do |team, arr|
arr << {
id: team.id,
conversations_count: @grouped_conversations_count[team.id],
avg_resolution_time: @grouped_avg_resolution_time[team.id],
avg_first_response_time: @grouped_avg_first_response_time[team.id],
avg_reply_time: @grouped_avg_reply_time[team.id]
}
end
end
end
@@ -1,16 +1,26 @@
class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
before_action :fetch_agent, except: [:create, :index]
before_action :fetch_agent, except: [:create, :index, :bulk_create]
before_action :check_authorization
before_action :find_user, only: [:create]
before_action :validate_limit, only: [:create]
before_action :create_user, only: [:create]
before_action :save_account_user, only: [:create]
before_action :validate_limit_for_bulk_create, only: [:bulk_create]
def index
@agents = agents
end
def create; end
def create
builder = AgentBuilder.new(
email: new_agent_params['email'],
name: new_agent_params['name'],
role: new_agent_params['role'],
availability: new_agent_params['availability'],
auto_offline: new_agent_params['auto_offline'],
inviter: current_user,
account: Current.account
)
builder.perform
end
def update
@agent.update!(agent_params.slice(:name).compact)
@@ -23,6 +33,30 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
head :ok
end
def bulk_create
emails = params[:emails]
emails.each do |email|
builder = AgentBuilder.new(
email: email,
name: email.split('@').first,
inviter: current_user,
account: Current.account
)
begin
builder.perform
rescue ActiveRecord::RecordInvalid => e
Rails.logger.info "[Agent#bulk_create] ignoring email #{email}, errors: #{e.record.errors}"
end
end
# This endpoint is used to bulk create agents during onboarding
# onboarding_step key in present in Current account custom attributes, since this is a one time operation
Current.account.custom_attributes.delete('onboarding_step')
Current.account.save!
head :ok
end
private
def check_authorization
@@ -33,47 +67,34 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
@agent = agents.find(params[:id])
end
def find_user
@user = User.find_by(email: new_agent_params[:email])
end
# TODO: move this to a builder and combine the save account user method into a builder
# ensure the account user association is also created in a single transaction
def create_user
return @user.send_confirmation_instructions if @user
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
end
def save_account_user
AccountUser.create!({
account_id: Current.account.id,
user_id: @user.id,
inviter_id: current_user.id
}.merge({
role: new_agent_params[:role],
availability: new_agent_params[:availability],
auto_offline: new_agent_params[:auto_offline]
}.compact))
end
def agent_params
params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline)
end
def new_agent_params
# intial string ensures the password requirements are met
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline)
.merge!(password: temp_password, password_confirmation: temp_password, inviter: current_user)
end
def agents
@agents ||= Current.account.users.order_by_full_name.includes(:account_users, { avatar_attachment: [:blob] })
end
def validate_limit_for_bulk_create
limit_available = params[:emails].count <= available_agent_count
render_payment_required('Account limit exceeded. Please purchase more licenses') unless limit_available
end
def validate_limit
render_payment_required('Account limit exceeded. Please purchase more licenses') if agents.count >= Current.account.usage_limits[:agents]
render_payment_required('Account limit exceeded. Please purchase more licenses') unless can_add_agent?
end
def available_agent_count
Current.account.usage_limits[:agents] - agents.count
end
def can_add_agent?
available_agent_count.positive?
end
def delete_user_record(agent)
@@ -46,7 +46,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
def export
column_names = params['column_names']
Account::ContactsExportJob.perform_later(Current.account.id, column_names)
Account::ContactsExportJob.perform_later(Current.account.id, column_names, Current.user.email)
head :ok, message: I18n.t('errors.contacts.export.success')
end
@@ -148,7 +148,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
end
def permitted_params
params.permit(:name, :identifier, :email, :phone_number, :avatar, :avatar_url, additional_attributes: {}, custom_attributes: {})
params.permit(:name, :identifier, :email, :phone_number, :avatar, :blocked, :avatar_url, additional_attributes: {}, custom_attributes: {})
end
def contact_custom_attributes
@@ -2,13 +2,13 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
RESULTS_PER_PAGE = 15
include DateRangeHelper
before_action :fetch_notification, only: [:update, :destroy, :snooze]
before_action :fetch_notification, only: [:update, :destroy, :snooze, :unread]
before_action :set_primary_actor, only: [:read_all]
before_action :set_current_page, only: [:index]
def index
@notifications = notification_finder.notifications
@unread_count = notification_finder.unread_count
@notifications = notification_finder.perform
@count = notification_finder.count
end
@@ -29,18 +29,33 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
render json: @notification
end
def unread
@notification.update(read_at: nil)
render json: @notification
end
def destroy
@notification.destroy
head :ok
end
def destroy_all
if params[:type] == 'read'
::Notification::DeleteNotificationJob.perform_later(Current.user, type: :read)
else
::Notification::DeleteNotificationJob.perform_later(Current.user, type: :all)
end
head :ok
end
def unread_count
@unread_count = notification_finder.unread_count
render json: @unread_count
end
def snooze
@notification.update(snoozed_until: parse_date_time(params[:snoozed_until].to_s)) if params[:snoozed_until]
updated_meta = (@notification.meta || {}).merge('last_snoozed_at' => nil)
@notification.update(snoozed_until: parse_date_time(params[:snoozed_until].to_s), meta: updated_meta) if params[:snoozed_until]
render json: @notification
end
+23 -3
View File
@@ -5,11 +5,13 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
before_action :ensure_account_name, only: [:create]
before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create]
rescue_from CustomExceptions::Account::InvalidEmail,
CustomExceptions::Account::InvalidParams,
CustomExceptions::Account::UserExists,
CustomExceptions::Account::UserErrors,
with: :render_error_response
@@ -38,11 +40,14 @@ class Api::V1::AccountsController < Api::BaseController
def cache_keys
expires_in 10.seconds, public: false, stale_while_revalidate: 5.minutes
render json: { cache_keys: get_cache_keys }, status: :ok
render json: { cache_keys: cache_keys_for_account }, status: :ok
end
def update
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :auto_resolve_duration))
@account.assign_attributes(account_params.slice(:name, :locale, :domain, :support_email, :auto_resolve_duration))
@account.custom_attributes.merge!(custom_attributes_params)
@account.custom_attributes['onboarding_step'] = 'invite_team' if @account.custom_attributes['onboarding_step'] == 'account_update'
@account.save!
end
def update_active_at
@@ -53,7 +58,18 @@ class Api::V1::AccountsController < Api::BaseController
private
def get_cache_keys
def ensure_account_name
# ensure that account_name and user_full_name is present
# this is becuase the account builder and the models validations are not triggered
# this change is to align the behaviour with the v2 accounts controller
# since these values are not required directly there
return if account_params[:account_name].present?
return if account_params[:user_full_name].present?
raise CustomExceptions::Account::InvalidParams.new({})
end
def cache_keys_for_account
{
label: fetch_value_for_key(params[:id], Label.name.underscore),
inbox: fetch_value_for_key(params[:id], Inbox.name.underscore),
@@ -70,6 +86,10 @@ class Api::V1::AccountsController < Api::BaseController
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
end
def custom_attributes_params
params.permit(:industry, :company_size, :timezone)
end
def check_signup_enabled
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
end
+12 -1
View File
@@ -10,7 +10,9 @@ class Api::V1::ProfilesController < Api::BaseController
@user.update!(password_params.except(:current_password))
end
@user.update!(profile_params)
@user.assign_attributes(profile_params)
@user.custom_attributes.merge!(custom_attributes_params)
@user.save!
end
def avatar
@@ -31,6 +33,11 @@ class Api::V1::ProfilesController < Api::BaseController
head :ok
end
def resend_confirmation
@user.send_confirmation_instructions unless @user.confirmed?
head :ok
end
private
def set_user
@@ -57,6 +64,10 @@ class Api::V1::ProfilesController < Api::BaseController
)
end
def custom_attributes_params
params.require(:profile).permit(:phone_number)
end
def password_params
params.require(:profile).permit(
:current_password,
@@ -14,6 +14,12 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
render json: summary_metrics
end
def bot_summary
summary = V2::ReportBuilder.new(Current.account, current_summary_params).bot_summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).bot_summary
render json: summary
end
def agents
@report_data = generate_agents_report
generate_csv('agents_report', 'api/v2/accounts/reports/agents')
@@ -48,6 +54,11 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
render json: conversation_metrics
end
def bot_metrics
bot_metrics = V2::Reports::BotMetricsBuilder.new(Current.account, params).metrics
render json: bot_metrics
end
private
def generate_csv(filename, template)
@@ -0,0 +1,36 @@
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :prepare_builder_params, only: [:agent, :team]
def agent
render_report_with(V2::Reports::AgentSummaryBuilder)
end
def team
render_report_with(V2::Reports::TeamSummaryBuilder)
end
private
def check_authorization
authorize :report, :view?
end
def prepare_builder_params
@builder_params = {
since: permitted_params[:since],
until: permitted_params[:until],
business_hours: ActiveModel::Type::Boolean.new.cast(permitted_params[:business_hours])
}
end
def render_report_with(builder_class)
builder = builder_class.new(account: Current.account, params: @builder_params)
data = builder.build
render json: data
end
def permitted_params
params.permit(:since, :until, :business_hours)
end
end
@@ -0,0 +1,69 @@
class Api::V2::AccountsController < Api::BaseController
include AuthHelper
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create]
rescue_from CustomExceptions::Account::InvalidEmail,
CustomExceptions::Account::UserExists,
CustomExceptions::Account::UserErrors,
with: :render_error_response
def create
@user, @account = AccountBuilder.new(
email: account_params[:email],
user_password: account_params[:password],
locale: account_params[:locale],
user: current_user
).perform
fetch_account_and_user_info
update_account_info if @account.present?
if @user
send_auth_headers(@user)
render 'api/v1/accounts/create', format: :json, locals: { resource: @user }
else
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
end
end
private
def account_attributes
{
custom_attributes: @account.custom_attributes.merge({ 'onboarding_step' => 'profile_update' })
}
end
def update_account_info
@account.update!(
account_attributes
)
end
def fetch_account_and_user_info; end
def fetch_account
@account = current_user.accounts.find(params[:id])
@current_account_user = @account.account_users.find_by(user_id: current_user.id)
end
def account_params
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
end
def check_signup_enabled
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
end
def validate_captcha
raise ActionController::InvalidAuthenticityToken, 'Invalid Captcha' unless ChatwootCaptcha.new(params[:h_captcha_client_response]).valid?
end
end
Api::V2::AccountsController.prepend_mod_with('Api::V2::AccountsController')
@@ -1,7 +1,8 @@
module AccessTokenAuthHelper
BOT_ACCESSIBLE_ENDPOINTS = {
'api/v1/accounts/conversations' => %w[toggle_status create],
'api/v1/accounts/conversations/messages' => ['create']
'api/v1/accounts/conversations' => %w[toggle_status toggle_priority create],
'api/v1/accounts/conversations/messages' => ['create'],
'api/v1/accounts/conversations/assignments' => ['create']
}.freeze
def ensure_access_token
@@ -25,6 +25,6 @@ module EnsureCurrentAccountHelper
end
def account_accessible_for_bot?(account)
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
render_unauthorized('Bot is not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
end
end
+1 -1
View File
@@ -55,7 +55,7 @@ class DashboardController < ActionController::Base
VAPID_PUBLIC_KEY: VapidService.public_key,
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
FACEBOOK_API_VERSION: 'v14.0',
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'),
IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', ''),
GIT_SHA: GIT_HASH
@@ -5,7 +5,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
skip_before_action :authenticate_user!, raise: false
def create
@user = User.find_by(email: params[:email])
@user = User.from_email(params[:email])
if @user
@user.send_reset_password_instructions
build_response(I18n.t('messages.reset_password_success'), 200)
@@ -33,7 +33,7 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
def process_sso_auth_token
return if params[:email].blank?
user = User.find_by(email: params[:email])
user = User.from_email(params[:email])
@resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token])
end
end
@@ -8,7 +8,7 @@ class Platform::Api::V1::UsersController < PlatformController
def show; end
def create
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
@resource = (User.from_email(user_params[:email]) || User.new(user_params))
@resource.skip_confirmation!
@resource.save!
@platform_app.platform_app_permissibles.find_or_create_by!(permissible: @resource)
@@ -1,15 +1,31 @@
class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController
include Events::Types
before_action :set_conversation, only: [:toggle_typing, :update_last_seen]
before_action :set_conversation, only: [:toggle_typing, :update_last_seen, :show, :toggle_status]
def index
@conversations = @contact_inbox.hmac_verified? ? @contact.conversations : @contact_inbox.conversations
end
def show; end
def create
@conversation = create_conversation
end
def toggle_status
# Check if the conversation is already resolved to prevent redundant operations
return if @conversation.resolved?
# Assign the conversation's contact as the resolver
# This step attributes the resolution action to the contact involved in the conversation
# If this assignment is not made, the system implicitly becomes the resolver by default
Current.contact = @conversation.contact
# Update the conversation's status to 'resolved' to reflect its closure
@conversation.status = :resolved
@conversation.save!
end
def toggle_typing
case params[:typing_status]
when 'on'
@@ -30,7 +46,11 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
private
def set_conversation
@conversation = @contact_inbox.contact.conversations.find_by!(display_id: params[:id])
@conversation = if @contact_inbox.hmac_verified?
@contact_inbox.contact.conversations.find_by!(display_id: params[:id])
else
@contact_inbox.conversations.find_by!(display_id: params[:id])
end
end
def create_conversation
@@ -22,19 +22,24 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
i.value = value
i.save!
end
# rubocop:disable Rails/I18nLocaleTexts
redirect_to super_admin_settings_path, notice: 'App Configs updated successfully'
# rubocop:enable Rails/I18nLocaleTexts
redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully"
end
private
def set_config
@config = params[:config]
@config = params[:config] || 'general'
end
def allowed_configs
@allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET]
@allowed_configs = case @config
when 'facebook'
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
when 'email'
['MAILER_INBOUND_EMAIL_DOMAIN']
else
%w[ENABLE_ACCOUNT_SIGNUP]
end
end
end
@@ -5,6 +5,10 @@
# If you want to add pagination or other controller-level concerns,
# you're free to overwrite the RESTful controller actions.
class SuperAdmin::ApplicationController < Administrate::ApplicationController
include ActionView::Helpers::TagHelper
include ActionView::Context
helper_method :render_vue_component
# authenticiation done via devise : SuperAdmin Model
before_action :authenticate_super_admin!
@@ -23,6 +27,17 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController
private
def render_vue_component(component_name, props = {})
html_options = {
id: 'app',
data: {
component_name: component_name,
props: props.to_json
}
}
content_tag(:div, '', html_options)
end
def invalid_action_perfomed
# rubocop:disable Rails/I18nLocaleTexts
flash[:error] = 'Invalid action performed'
@@ -28,8 +28,7 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
end
def sha
sha = `git rev-parse HEAD`
@metrics['Git SHA'] = sha.presence || 'n/a'
@metrics['Git SHA'] = GIT_HASH
end
def postgres_status
+7 -1
View File
@@ -169,6 +169,12 @@ class ConversationFinder
)
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
@conversations.send(sort_by, sort_order).page(current_page).per(ENV.fetch('CONVERSATION_RESULTS_PER_PAGE', '25').to_i)
@conversations = @conversations.send(sort_by, sort_order)
if params[:updated_within].present?
@conversations.where('conversations.updated_at > ?', Time.zone.now - params[:updated_within].to_i.seconds)
else
@conversations.page(current_page).per(ENV.fetch('CONVERSATION_RESULTS_PER_PAGE', '25').to_i)
end
end
end
+16 -7
View File
@@ -10,8 +10,8 @@ class NotificationFinder
set_up
end
def perform
notifications
def notifications
@notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: sort_order)
end
def unread_count
@@ -26,22 +26,31 @@ class NotificationFinder
def set_up
find_all_notifications
filter_by_status
filter_snoozed_notifications
fitler_read_notifications
end
def find_all_notifications
@notifications = current_user.notifications.where(account_id: @current_account.id)
end
def filter_by_status
@notifications = @notifications.where('snoozed_until > ?', DateTime.now.utc) if params[:status] == 'snoozed'
def filter_snoozed_notifications
@notifications = @notifications.where(snoozed_until: nil) unless type_included?('snoozed')
end
def fitler_read_notifications
@notifications = @notifications.where(read_at: nil) unless type_included?('read')
end
def type_included?(type)
(params[:includes] || []).include?(type)
end
def current_page
params[:page] || 1
end
def notifications
@notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: :desc)
def sort_order
params[:sort_order] || :desc
end
end
+10 -10
View File
@@ -15,8 +15,8 @@ module Api::V2::Accounts::HeatmapHelper
dates = data.pluck(:date).uniq.sort
# add the dates as the first row, leave an empty cell for the hour column
# e.g. [nil, '2023-01-01', '2023-1-02', '2023-01-03']
result_arr << ([nil] + dates)
# e.g. ['Start of the hour', '2023-01-01', '2023-1-02', '2023-01-03']
result_arr << (['Start of the hour'] + dates)
# group the data by hour, we do not need to sort it, because the data is already sorted
# given it starts from the beginning of the day
@@ -25,7 +25,7 @@ module Api::V2::Accounts::HeatmapHelper
# value = [{date: 2023-01-01, value: 1}, {date: 2023-01-02, value: 1}, {date: 2023-01-03, value: 1}, ...]
data.group_by { |d| d[:hour] }.each do |hour, items|
# create a new row for each hour
row = [hour]
row = [format('%02d:00', hour)]
# group the items by date, so we can easily access the value for each date
# grouped values will be a hasg with the date as the key, and the value as the value
@@ -37,7 +37,7 @@ module Api::V2::Accounts::HeatmapHelper
row << (grouped_values[date][0][:value] if grouped_values[date].is_a?(Array))
end
# row will look like [22, 0, 0, 1, 4, 6, 7, 4]
# row will look like ['22:00', 0, 0, 1, 4, 6, 7, 4]
# add the row to the result array
result_arr << row
@@ -46,12 +46,12 @@ module Api::V2::Accounts::HeatmapHelper
# return the resultant array
# the result looks like this
# [
# [nil, '2023-01-01', '2023-1-02', '2023-01-03'],
# [0, 0, 0, 0],
# [1, 0, 0, 0],
# [2, 0, 0, 0],
# [3, 0, 0, 0],
# [4, 0, 0, 0],
# ['Start of the hour', '2023-01-01', '2023-1-02', '2023-01-03'],
# ['00:00', 0, 0, 0],
# ['01:00', 0, 0, 0],
# ['02:00', 0, 0, 0],
# ['03:00', 0, 0, 0],
# ['04:00', 0, 0, 0],
# ]
result_arr
end
@@ -45,12 +45,8 @@ module Api::V2::Accounts::ReportsHelper
def generate_readable_report_metrics(report_metric)
[
report_metric[:conversations_count],
time_to_minutes(report_metric[:avg_first_response_time]),
time_to_minutes(report_metric[:avg_resolution_time])
Reports::TimeFormatPresenter.new(report_metric[:avg_first_response_time]).format,
Reports::TimeFormatPresenter.new(report_metric[:avg_resolution_time]).format
]
end
def time_to_minutes(time_in_seconds)
(time_in_seconds / 60).to_i
end
end
+83
View File
@@ -0,0 +1,83 @@
module ContactHelper
def parse_name(full_name)
# If the input is nil or not a string, return a hash with all values set to nil
return default_name_hash if invalid_name?(full_name)
# If the input is a number, return a hash with the number as the first name
return numeric_name_hash(full_name) if valid_number?(full_name)
full_name = full_name.squish
# If full name consists of only one word, consider it as the first name
return single_word_name_hash(full_name) if single_word?(full_name)
parts = split_name(full_name)
parts = handle_conjunction(parts)
build_name_hash(parts)
end
private
def default_name_hash
{ first_name: nil, last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
end
def invalid_name?(full_name)
!full_name.is_a?(String) || full_name.empty?
end
def numeric_name_hash(full_name)
{ first_name: full_name, last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
end
def valid_number?(full_name)
TelephoneNumber.parse(full_name).valid?
end
def single_word_name_hash(full_name)
{ first_name: full_name, last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
end
def single_word?(full_name)
full_name.split.size == 1
end
def split_name(full_name)
full_name.split
end
def handle_conjunction(parts)
conjunctions = ['and', '&']
parts.each_index do |i|
next unless conjunctions.include?(parts[i]) && i.positive?
parts[i - 1] = [parts[i - 1], parts[i + 1]].join(' ')
parts.delete_at(i)
parts.delete_at(i)
end
parts
end
def build_name_hash(parts)
suffix = parts.pop if parts.last.match?(/(\w+\.|[IVXLM]+|[A-Z]+)$/)
last_name = parts.pop
prefix = parts.shift if parts.first.match?(/^\w+\./)
first_name = parts.shift
middle_name = parts.join(' ')
hash = {
first_name: first_name,
last_name: last_name,
prefix: prefix,
middle_name: middle_name,
suffix: suffix
}
# Reverse name if "," was used in Last, First notation.
if hash[:first_name] =~ /,$/
hash[:first_name] = hash[:last_name]
hash[:last_name] = Regexp.last_match.pre_match
end
hash
end
end
+18
View File
@@ -32,6 +32,14 @@ module ReportHelper
(get_grouped_values resolutions).count
end
def bot_resolutions_count
(get_grouped_values bot_resolutions).count
end
def bot_handoffs_count
(get_grouped_values bot_handoffs).count
end
def conversations
scope.conversations.where(account_id: account.id, created_at: range)
end
@@ -49,6 +57,16 @@ module ReportHelper
conversations: { status: :resolved }, created_at: range).distinct
end
def bot_resolutions
scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_resolved,
conversations: { status: :resolved }, created_at: range).distinct
end
def bot_handoffs
scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_handoff,
created_at: range).distinct
end
def avg_first_response_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
@@ -0,0 +1,9 @@
module SuperAdmin::AccountFeaturesHelper
def self.account_features
YAML.safe_load(Rails.root.join('config/features.yml').read).freeze
end
def self.account_premium_features
account_features.filter { |feature| feature['premium'] }.pluck('name')
end
end
+9 -2
View File
@@ -2,13 +2,14 @@
<div
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
id="app"
class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
class="flex-grow-0 w-full h-full min-h-0 app-wrapper"
:class="{ 'app-rtl--wrapper': isRTLView }"
:dir="isRTLView ? 'rtl' : 'ltr'"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="currentAccountId">
<payment-pending-banner />
<pending-email-verification-banner v-if="hideOnOnboardingView" />
<payment-pending-banner v-if="hideOnOnboardingView" />
<upgrade-banner />
</template>
<transition name="fade" mode="out-in">
@@ -32,10 +33,12 @@ import NetworkNotification from './components/NetworkNotification.vue';
import UpdateBanner from './components/app/UpdateBanner.vue';
import UpgradeBanner from './components/app/UpgradeBanner.vue';
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer.vue';
import rtlMixin from 'shared/mixins/rtlMixin';
import { setColorTheme } from './helper/themeHelper';
import { isOnOnboardingView } from 'v3/helpers/RouteHelper';
import {
registerSubscription,
verifyServiceWorkerExistence,
@@ -52,6 +55,7 @@ export default {
PaymentPendingBanner,
WootSnackbarBox,
UpgradeBanner,
PendingEmailVerificationBanner,
},
mixins: [rtlMixin],
@@ -76,6 +80,9 @@ export default {
const { accounts = [] } = this.currentUser || {};
return accounts.length > 0;
},
hideOnOnboardingView() {
return !isOnOnboardingView(this.$route);
},
},
watch: {
+8
View File
@@ -1,9 +1,17 @@
/* global axios */
import ApiClient from './ApiClient';
class Agents extends ApiClient {
constructor() {
super('agents', { accountScoped: true });
}
bulkInvite({ emails }) {
return axios.post(`${this.url}/bulk_create`, {
emails,
});
}
}
export default new Agents();
+7 -2
View File
@@ -29,11 +29,12 @@ export default {
return fetchPromise;
},
hasAuthCookie() {
return !!Cookies.getJSON('cw_d_session_info');
return !!Cookies.get('cw_d_session_info');
},
getAuthData() {
if (this.hasAuthCookie()) {
return Cookies.getJSON('cw_d_session_info');
const savedAuthInfo = Cookies.get('cw_d_session_info');
return JSON.parse(savedAuthInfo || '{}');
}
return false;
},
@@ -97,4 +98,8 @@ export default {
},
});
},
resendConfirmation() {
const urlData = endPoints('resendConfirmation');
return axios.post(urlData.url);
},
};
@@ -47,6 +47,10 @@ const endPoints = {
setActiveAccount: {
url: '/api/v1/profile/set_active_account',
},
resendConfirmation: {
url: '/api/v1/profile/resend_confirmation',
},
};
export default page => {
+30 -2
View File
@@ -6,8 +6,16 @@ class NotificationsAPI extends ApiClient {
super('notifications', { accountScoped: true });
}
get(page) {
return axios.get(`${this.url}?page=${page}`);
get({ page, status, type, sortOrder }) {
const includesFilter = [status, type].filter(value => !!value);
return axios.get(this.url, {
params: {
page,
sort_order: sortOrder,
includes: includesFilter,
},
});
}
getNotifications(contactId) {
@@ -25,9 +33,29 @@ class NotificationsAPI extends ApiClient {
});
}
unRead(id) {
return axios.post(`${this.url}/${id}/unread`);
}
readAll() {
return axios.post(`${this.url}/read_all`);
}
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
deleteAll({ type = 'all' }) {
return axios.post(`${this.url}/destroy_all`, {
type,
});
}
snooze({ id, snoozedUntil = null }) {
return axios.post(`${this.url}/${id}/snooze`, {
snoozed_until: snoozedUntil,
});
}
}
export default new NotificationsAPI();
+9
View File
@@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class SlaAPI extends ApiClient {
constructor() {
super('sla_policies', { accountScoped: true });
}
}
export default new SlaAPI();
@@ -10,4 +10,29 @@ describe('#AgentAPI', () => {
expect(agents).toHaveProperty('update');
expect(agents).toHaveProperty('delete');
});
describe('API calls', () => {
const originalAxios = window.axios;
const axiosMock = {
post: jest.fn(() => Promise.resolve()),
};
beforeEach(() => {
window.axios = axiosMock;
});
afterEach(() => {
window.axios = originalAxios;
});
it('#bulkInvite', () => {
agents.bulkInvite({ emails: ['hello@hi.com'] });
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/agents/bulk_create',
{
emails: ['hello@hi.com'],
}
);
});
});
});
@@ -27,11 +27,37 @@ describe('#NotificationAPI', () => {
window.axios = originalAxios;
});
it('#get', () => {
notificationsAPI.get(1);
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/notifications?page=1'
);
describe('#get', () => {
it('generates the API call if both params are available', () => {
notificationsAPI.get({
page: 1,
status: 'snoozed',
type: 'read',
sortOrder: 'desc',
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/notifications', {
params: {
page: 1,
sort_order: 'desc',
includes: ['snoozed', 'read'],
},
});
});
it('generates the API call if one of the params are available', () => {
notificationsAPI.get({
page: 1,
type: 'read',
sortOrder: 'desc',
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/notifications', {
params: {
page: 1,
sort_order: 'desc',
includes: ['read'],
},
});
});
});
it('#getNotifications', () => {
@@ -65,5 +91,30 @@ describe('#NotificationAPI', () => {
'/api/v1/notifications/read_all'
);
});
it('#snooze', () => {
notificationsAPI.snooze({ id: 1, snoozedUntil: 12332211 });
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/notifications/1/snooze',
{
snoozed_until: 12332211,
}
);
});
it('#delete', () => {
notificationsAPI.delete(1);
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/notifications/1');
});
it('#deleteAll', () => {
notificationsAPI.deleteAll({ type: 'all' });
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/notifications/destroy_all',
{
type: 'all',
}
);
});
});
});
@@ -1,58 +0,0 @@
.button {
font-family: $body-font-family;
font-weight: $font-weight-medium;
&.round {
border-radius: 1000px;
}
}
select {
height: 2.5rem;
}
.card {
margin-bottom: var(--space-small);
padding: var(--space-normal);
}
code {
border: 0;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
'"Liberation Mono"', '"Courier New"', 'monospace';
font-size: $font-size-mini;
&.hljs {
background: $color-background;
border-radius: var(--border-radius-large);
padding: $space-two;
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100;
}
}
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-capitalize {
text-transform: capitalize;
}
.cursor-pointer {
cursor: pointer;
}
// remove when grid gutters are fixed
.columns.with-right-space {
padding-right: var(--space-normal);
}
.badge {
border-radius: var(--border-radius-normal);
}
.padding-right-small {
padding-right: var(--space-one);
}
@@ -1,623 +0,0 @@
// Foundation for Sites Settings
// -----------------------------
//
// Table of Contents:
//
// 1. Global
// 2. Breakpoints
// 3. The Grid
// 4. Base Typography
// 5. Typography Helpers
// 6. Abide
// 7. Accordion
// 8. Accordion Menu
// 9. Badge
// 10. Breadcrumbs
// 11. Button
// 12. Button Group
// 13. Callout
// 14. Card
// 15. Close Button
// 16. Drilldown
// 17. Dropdown
// 18. Dropdown Menu
// 19. Forms
// 20. Label
// 21. Media Object
// 22. Menu
// 23. Meter
// 24. Off-canvas
// 25. Orbit
// 26. Pagination
// 27. Progress Bar
// 28. Responsive Embed
// 29. Reveal
// 30. Slider
// 31. Switch
// 32. Table
// 33. Tabs
// 34. Thumbnail
// 35. Title Bar
// 36. Tooltip
// 37. Top Bar
@import '~foundation-sites/scss/util/util';
// 1. Global
// ---------
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
secondary: #5d7592,
success: #44ce4b,
warning: #ffc532,
alert: #ff382d);
$light-gray: #c0ccda;
$medium-gray: #8492a6;
$dark-gray: $color-gray;
$black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'PlusJakarta',
-apple-system,
system-ui,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Tahoma,
Arial,
sans-serif;
$body-antialiased: true;
$global-margin: $space-small;
$global-padding: $space-small;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;
$global-text-direction: ltr;
$global-flexbox: false;
$print-transparent-backgrounds: true;
@include add-foundation-colors;
// 2. Breakpoints
// --------------
$breakpoints: (small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1400px,
xxxlarge: 1600px,
);
$print-breakpoint: large;
$breakpoint-classes: (small medium large);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (small: $zero,
medium: $zero);
$grid-column-align-edge: true;
$block-grid-max: 8;
// 4. Base Typography
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $font-weight-medium;
$header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.3125rem;
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
"h4": ("font-size": 18),
"h5": ("font-size": 17),
"h6": ("font-size": 16)),
medium: ("h1": ("font-size": 48),
"h2": ("font-size": 40),
"h3": ("font-size": 31),
"h4": ("font-size": 25),
"h5": ("font-size": 20),
"h6": ("font-size": 16)));
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.65;
$paragraph-margin-bottom: var(--space-small);
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
$code-font-family: $font-family-monospace;
$code-font-weight: $global-weight-normal;
$code-background: $light-gray;
$code-border: 1px solid $medium-gray;
$code-padding: rem-calc(2 5 1);
$anchor-color: $primary-color;
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
$anchor-text-decoration: none;
$anchor-text-decoration-hover: none;
$hr-width: $global-width;
$hr-border: 1px solid $medium-gray;
$hr-margin: rem-calc(20) auto;
$list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 0.78125rem;
$list-nested-side-margin: 0.78125rem;
$defnlist-margin-bottom: 0.6875rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.1875rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$cite-pseudo-content: '\2014 \0020';
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
$keystroke-padding: rem-calc(2 4 0);
$keystroke-radius: $global-radius;
$abbr-underline: 1px dotted $black;
// 5. Typography Helpers
// ---------------------
$lead-font-size: $global-font-size * 1.25;
$lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.125rem;
$subheader-margin-bottom: 0.3125rem;
$stat-font-size: 1.5625rem;
// 6. Abide
// --------
$abide-inputs: true;
$abide-labels: true;
$input-background-invalid: get-color(alert);
$form-label-color-invalid: get-color(alert);
$input-error-color: get-color(alert);
$input-error-font-size: rem-calc(12);
$input-error-font-weight: $global-weight-bold;
// 7. Accordion
// ------------
$accordion-background: $white;
$accordion-plusminus: true;
$accordion-title-font-size: rem-calc(12);
$accordion-item-color: $primary-color;
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 0.78125rem 0.625rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: $body-font-color;
$accordion-content-padding: 0.625rem;
// 8. Accordion Menu
// -----------------
$accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color;
$accordionmenu-arrow-size: 6px;
// 9. Badge
// --------
$badge-background: $primary-color;
$badge-color: $white;
$badge-color-alt: $black;
$badge-palette: $foundation-palette;
$badge-padding: var(--space-smaller);
$badge-minwidth: 2.1em;
$badge-font-size: var(--font-size-nano);
// 10. Breadcrumbs
// ---------------
$breadcrumbs-margin: 0 0 $global-margin 0;
$breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.46875rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
// 11. Button
// ----------
$button-padding: var(--space-smaller) 1em;
$button-margin: 0 0 $global-margin 0;
$button-fill: solid;
$button-background: $primary-color;
$button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $white;
$button-radius: var(--border-radius-normal);
$button-sizes: (tiny: var(--font-size-micro),
small: var(--font-size-mini),
default: var(--font-size-small),
large: var(--font-size-medium));
$button-palette: $foundation-palette;
$button-opacity-disabled: 0.4;
$button-background-hover-lightness: -20%;
$button-hollow-hover-lightness: -50%;
$button-transition: background-color 0.25s ease-out,
color 0.25s ease-out;
// 12. Button Group
// ----------------
$buttongroup-margin: 0;
$buttongroup-spacing: 0;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
$buttongroup-radius-on-each: false;
// 13. Callout
// -----------
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 0.625rem 0;
$callout-padding: 0.625rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
$callout-link-tint: 30%;
// 14. Card
// --------
$card-background: $white;
$card-font-color: $body-font-color;
$card-divider-background: $light-gray;
$card-border: 1px solid var(--color-border);
$card-shadow: var(--shadow-small);
$card-border-radius: var(--border-radius-normal);
$card-padding: var(--space-small);
$card-margin: $global-margin;
// 15. Close Button
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: (small: 0.66rem,
medium: 1rem);
$closebutton-offset-vertical: (small: 0.33em,
medium: 0.5rem);
$closebutton-size: (small: 1.5em,
medium: 2em);
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
// 16. Drilldown
// -------------
$drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true;
$drilldown-arrow-color: $primary-color;
$drilldown-arrow-size: 6px;
$drilldown-background: $white;
// 17. Dropdown
// ------------
$dropdown-padding: 0.625rem;
$dropdown-background: $body-background;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (tiny: 100px,
small: 200px,
large: 400px);
// 18. Dropdown Menu
// -----------------
$dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-arrow-size: 6px;
$dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white;
$dropdownmenu-border: 1px solid $medium-gray;
// 19. Forms
// ---------
$fieldset-border: 1px solid $light-gray;
$fieldset-padding: $space-two;
$fieldset-margin: $space-one $zero;
$legend-padding: rem-calc(0 3);
$form-spacing: $space-normal;
$helptext-color: $color-body;
$helptext-font-size: $font-size-small;
$helptext-font-style: italic;
$input-prefix-color: $color-body;
$input-prefix-background: var(--b-100);
$input-prefix-border: 1px solid $color-border;
$input-prefix-padding: 0.625rem;
$form-label-color: $color-body;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $font-weight-medium;
$form-label-line-height: 1.8;
$select-background: $white;
$select-triangle-color: $dark-gray;
$select-radius: var(--border-radius-normal);
$input-color: $color-body;
$input-placeholder-color: $light-gray;
$input-font-family: inherit;
$input-font-size: $font-size-default;
$input-font-weight: $global-weight-normal;
$input-background: $white;
$input-background-focus: $white;
$input-background-disabled: $light-gray;
$input-border: 1px solid var(--s-200);
$input-border-focus: 1px solid lighten($primary-color, 15%);
$input-shadow: 0;
$input-shadow-focus: 0;
$input-cursor-disabled: not-allowed;
$input-transition: border-color 0.25s ease-in-out;
$input-number-spinners: true;
$input-radius: var(--border-radius-normal);
$form-button-radius: var(--border-radius-normal);
// 20. Label
// ---------
$label-background: $white;
$label-color: $black;
$label-color-alt: $black;
$label-palette: $foundation-palette;
$label-font-size: $font-size-mini;
$label-padding: $space-smaller $space-small;
$label-radius: var(--border-radius-small);
// 21. Media Object
// ----------------
$mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%;
// 22. Menu
// --------
$menu-margin: 0;
$menu-margin-nested: $space-medium;
$menu-item-padding: $space-slab;
$menu-item-color-active: $white;
$menu-item-background-active: $color-background;
$menu-icon-spacing: 0.15625rem;
$menu-item-background-hover: $light-gray;
$menu-border: $light-gray;
// 23. Meter
// ---------
$meter-height: 0.625rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
$meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-vertical-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
$offcanvas-push-zindex: 1;
$offcanvas-overlap-zindex: 10;
$offcanvas-reveal-zindex: 1;
$offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content';
// 25. Orbit
// ---------
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 0.75rem;
$orbit-bullet-margin: 0.0625rem;
$orbit-bullet-margin-top: 0.5rem;
$orbit-bullet-margin-bottom: 0.5rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 0.625rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 0.625rem;
$orbit-control-zindex: 10;
// 26. Pagination
// --------------
$pagination-font-size: rem-calc(14);
$pagination-margin-bottom: $global-margin;
$pagination-item-color: $black;
$pagination-item-padding: rem-calc(3 10);
$pagination-item-spacing: rem-calc(1);
$pagination-radius: $global-radius;
$pagination-item-background-hover: $light-gray;
$pagination-item-background-current: $primary-color;
$pagination-item-color-current: $white;
$pagination-item-color-disabled: $medium-gray;
$pagination-ellipsis-color: $black;
$pagination-mobile-items: false;
$pagination-mobile-current-item: false;
$pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 0.625rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
$progress-radius: $global-radius;
// 28. Responsive Embed
// --------------------
$responsive-embed-margin-bottom: rem-calc(16);
$responsive-embed-ratios: (default: 4 by 3,
widescreen: 16 by 9);
// 29. Reveal
// ----------
$reveal-background: $white;
$reveal-width: 600px;
$reveal-max-width: $global-width;
$reveal-padding: $global-padding;
$reveal-border: 1px solid $medium-gray;
$reveal-radius: $global-radius;
$reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45);
// 30. Slider
// ----------
$slider-width-vertical: 0.3125rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.3125rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 0.875rem;
$slider-handle-width: 0.875rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
// 31. Switch
// ----------
$switch-background: $light-gray;
$switch-background-active: $primary-color;
$switch-height: $space-two;
$switch-height-tiny: $space-slab;
$switch-height-small: $space-normal;
$switch-height-large: $space-large;
$switch-radius: $space-large;
$switch-margin: $global-margin;
$switch-paddle-background: $white;
$switch-paddle-offset: $space-micro;
$switch-paddle-radius: $space-large;
$switch-paddle-transition: all 0.15s ease-out;
// 32. Table
// ---------
$table-background: transparent;
$table-color-scale: 5%;
$table-border: 1px solid transparent;
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background,
$table-color-scale + $table-hover-scale);
$table-is-striped: false;
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
$table-head-row-hover: darken($table-head-background, $table-hover-scale);
$table-foot-background: smart-scale($table-background, $table-color-scale);
$table-foot-row-hover: darken($table-foot-background, $table-hover-scale);
$table-head-font-color: $body-font-color;
$table-foot-font-color: $body-font-color;
$show-header-for-stacked: false;
// 33. Tabs
// --------
$tab-margin: 0;
$tab-background: transparent;
$tab-background-active: transparent;
$tab-item-font-size: $font-size-small;
$tab-item-background-hover: transparent;
$tab-item-padding: $space-one $zero;
$tab-color: $primary-color;
$tab-active-color: $primary-color;
$tab-expand-max: 6;
$tab-content-background: transparent;
$tab-content-border: transparent;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 0.625rem;
// 34. Thumbnail
// -------------
$thumbnail-border: solid 4px $white;
$thumbnail-margin-bottom: $global-margin;
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
$thumbnail-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius;
// 35. Title Bar
// -------------
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.3125rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.15625rem;
// 36. Tooltip
// -----------
$has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.46875rem;
$tooltip-font-size: $font-size-mini;
$tooltip-pip-width: 0.46875rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 37. Top Bar
// -----------
$topbar-padding: 0.3125;
$topbar-background: $light-gray;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;
// Internal variable that contains the flex justifying options
$-zf-flex-justify: -zf-flex-justify($global-text-direction);
$menu-items-padding: $space-one;
$xy-grid: false;
@@ -1,64 +1,22 @@
.bg-light {
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {
@include flex-align(center, middle);
display: flex;
}
.bottom-space-fix {
margin-bottom: auto;
}
.full-height {
@include full-height();
}
// loader class
.spinner {
@include color-spinner();
display: inline-block;
height: $space-medium;
padding: $zero $space-medium;
position: relative;
vertical-align: middle;
width: $space-medium;
@apply inline-block h-6 py-0 px-6 relative align-middle w-6;
&.message {
@include normal-shadow;
background: $color-white;
border-radius: $space-large;
left: 0;
margin: $space-slab auto;
padding: $space-normal;
top: 0;
@apply bg-white dark:bg-slate-800 rounded-full left-0 my-3 mx-auto p-4 top-0;
&::before {
margin-left: -$space-slab;
margin-top: -$space-slab;
@apply -ml-3 -mt-3;
}
}
&.small {
height: $space-normal;
width: $space-normal;
@apply h-4 w-4;
&::before {
height: $space-normal;
margin-top: -$space-small;
width: $space-normal;
@apply h-4 -mt-2 w-4;
}
}
}
.justify-space-between {
justify-content: space-between;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
@@ -1,5 +1,19 @@
// scss-lint:disable SpaceAfterPropertyColon
// @import 'shared/assets/fonts/inter';
html,
body {
font-family:
'PlusJakarta',
Inter,
-apple-system,
system-ui,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
sans-serif !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
height: 100%;
@@ -131,37 +131,12 @@
}
}
.search-header--wrap {
.search--input {
text-align: right;
}
.layout-switch__container {
transform: rotate(180deg);
}
}
// Basic filter dropdown
.basic-filter {
left: 0;
right: unset;
}
// Card label
.label-container {
.label {
margin-left: var(--space-smaller);
margin-right: 0;
}
}
// Secondary sidebar toggle button
.toggle-sidebar {
margin-left: 0;
margin-right: var(--space-minus-small);
transform: rotate(180deg);
}
// Bulk actions
.bulk-action__container {
.triangle {
@@ -202,22 +177,6 @@
}
}
// Notification panel
.notification-wrap {
left: 0;
right: var(--space-jumbo);
.action-button {
margin-left: var(--space-small);
margin-right: 0;
}
.notification-content--wrap {
margin-left: 0;
margin-right: var(--space-small);
}
}
// Help center
.article-container .row--article-block {
td:last-child {
@@ -324,10 +283,6 @@
// Other changes
.account-selector--wrap {
direction: initial;
}
.colorpicker--chrome {
direction: initial;
}
@@ -347,9 +302,4 @@
.contact--form .input-group {
direction: initial;
}
// scss-lint:disable QualifyingElement
.dropdown-menu--header > span.title {
text-align: right;
}
}
@@ -1,33 +0,0 @@
.page-title {
font-size: $font-size-big;
}
.page-sub-title {
font-size: $font-size-large;
word-wrap: break-word;
}
.block-title {
font-size: $font-size-medium;
}
.sub-block-title {
font-size: $font-size-default;
}
.text-block-title {
font-size: $font-size-small;
}
.text-muted {
color: var(--s-300);
}
a {
font-size: $font-size-small;
}
p {
font-size: $font-size-small;
word-spacing: .12em;
}
@@ -1,73 +0,0 @@
.margin-bottom-small {
margin-bottom: var(--space-small);
}
.margin-right-smaller {
margin-right: var(--space-smaller);
}
.margin-left-minus-slab {
margin-left: var(--space-minus-slab);
}
.margin-right-minus-slab {
margin-right: var(--space-minus-slab);
}
.fs-small {
font-size: var(--font-size-small);
}
.fs-default {
font-size: var(--font-size-default);
}
.fw-medium {
font-weight: var(--font-weight-medium);
}
.p-normal {
padding: var(--space-normal);
}
.overflow-scroll {
overflow: scroll;
}
.overflow-auto {
overflow: auto;
}
.overflow-hidden {
overflow: hidden;
}
.border-right {
@apply border-r border-slate-50 dark:border-slate-700;
}
.border-left {
border-left: 1px solid var(--color-border);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flex-between {
align-items: center;
display: flex;
justify-content: space-between;
}
.flex-end {
display: flex;
justify-content: end;
}
.flex-align-center {
align-items: center;
display: flex;
}
@@ -15,63 +15,28 @@
@import 'variables';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'formulate';
@import 'date-picker';
@import 'foundation-sites/scss/foundation';
@include foundation-everything($flex: true);
@include foundation-prototype-text-utilities;
@include foundation-prototype-text-transformation;
@include foundation-prototype-text-decoration;
@include foundation-prototype-font-styling;
@include foundation-prototype-list-style-type;
@include foundation-prototype-rounded;
@include foundation-prototype-bordered;
@include foundation-prototype-shadow;
@include foundation-prototype-separator;
@include foundation-prototype-overflow;
@include foundation-prototype-display;
@include foundation-prototype-position;
@include foundation-prototype-border-box;
@include foundation-prototype-border-none;
@include foundation-prototype-sizing;
@include foundation-prototype-spacing;
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'rtl';
@import 'widgets/base';
@import 'widgets/buttons';
@import 'widgets/conv-header';
@import 'widgets/conversation-card';
@import 'widgets/conversation-view';
@import 'widgets/forms';
@import 'widgets/login';
@import 'widgets/modal';
@import 'widgets/reply-box';
@import 'widgets/report';
@import 'widgets/search-box';
@import 'widgets/sidemenu';
@import 'widgets/snackbar';
@import 'widgets/states';
@import 'widgets/status-bar';
@import 'widgets/tabs';
@import 'widgets/woot-tables';
@import 'views/settings/inbox';
@import 'views/settings/integrations';
@import 'plugins/multiselect';
@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;
}
.hide {
@apply hidden;
}
@@ -1,46 +1,7 @@
.dropdown-pane {
@include elegant-card;
@include border-light;
box-sizing: content-box;
padding: var(--space-small);
width: fit-content;
z-index: var(--z-index-very-high);
@apply border rounded-lg hidden relative invisible shadow-lg border-slate-25 dark:border-slate-700 box-content p-2 w-fit z-[9999];
&.dropdown-pane--open {
@apply bg-white dark:bg-slate-800;
display: block;
visibility: visible;
}
&.dropdowm--bottom {
&::before {
@include arrow(top, var(--color-border-light), 14px);
position: absolute;
right: 6px;
top: -14px;
}
&::after {
@include arrow(top, $color-white, var(--space-slab));
position: absolute;
right: var(--space-small);
top: -12px;
}
}
&.dropdowm--top {
&::before {
@include arrow(bottom, var(--color-border-light), 14px);
bottom: -14px;
position: absolute;
right: 6px;
}
&::after {
@include arrow(bottom, $color-white, var(--space-slab));
bottom: -12px;
position: absolute;
right: var(--space-small);
}
@apply bg-white absolute dark:bg-slate-800 block visible;
}
}
@@ -97,6 +97,10 @@
.multiselect__tags {
@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;
input {
@apply border-0 border-none;
}
}
.multiselect__tags-wrap {
@@ -149,7 +153,6 @@
}
.multiselect-wrap--small {
.multiselect__tags,
.multiselect__input,
.multiselect {
@@ -180,7 +183,6 @@
.multiselect--disabled .multiselect__select {
@apply bg-transparent;
}
}
.multiselect-wrap--medium {
@@ -13,20 +13,14 @@
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'foundation-sites/scss/foundation';
@include foundation-prototype-spacing;
@include foundation-everything($flex: true);
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'widgets/buttons';
@import 'widgets/forms';
@import 'widgets/base';
@import 'plugins/multiselect';
@@ -36,7 +30,6 @@
@import 'tailwindcss/utilities';
@import 'widget/assets/scss/utilities';
html,
body {
font-family: 'PlusJakarta', sans-serif;
@@ -1,112 +1 @@
.settings {
@apply overflow-auto;
}
.wizard-box {
.item {
@apply cursor-pointer py-4 pr-4 pl-6 relative;
&::before,
&::after {
@apply bg-slate-75 dark:bg-slate-600 content-[''] h-full absolute top-5 w-0.5;
}
&::before {
@apply h-4 top-0;
}
&:first-child {
&::before {
@apply h-0;
}
}
&:last-child {
&::after {
@apply h-0;
}
}
&.active {
h3 {
@apply text-woot-500 dark:text-woot-500;
}
.step {
@apply bg-woot-500 dark:bg-woot-500;
}
}
&.over {
&::after {
@apply bg-woot-500 dark:bg-woot-500;
}
.step {
@apply bg-woot-500 dark:bg-woot-500;
}
& + .item {
&::before {
@apply bg-woot-500 dark:bg-woot-500;
}
}
}
h3 {
@apply text-slate-800 dark:text-slate-100 text-base pl-6;
}
.completed {
@apply text-green-500 dark:text-green-500 ml-1;
}
p {
@apply text-slate-600 dark:text-slate-300 text-sm m-0 pl-6;
}
.step {
@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 {
@apply text-xxs;
}
}
}
}
.wizard-body {
@apply border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6;
&.height-auto {
@apply h-auto;
}
}
.settings--content {
@apply my-2 mx-8;
.title {
@apply font-medium;
}
.code {
@apply bg-slate-50 dark:bg-slate-800 overflow-auto p-2.5 whitespace-nowrap;
code {
@apply bg-transparent border-0;
}
}
}
.login-init {
@apply pt-[30%] text-center;
p {
@apply p-6;
}
> a > img {
@apply w-60;
}
}
// to be removed
@@ -0,0 +1,146 @@
// scss-lint:disable QualifyingElement
// Base typography
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-medium text-slate-800 dark:text-slate-50;
}
p {
text-rendering: optimizeLegibility;
word-spacing: 0.12em;
@apply mb-2 leading-[1.65] text-sm;
a {
@apply text-woot-500 dark:text-woot-500 cursor-pointer;
}
}
a {
@apply text-sm;
}
hr {
@apply clear-both max-w-full h-0 my-5 mx-0 border-slate-300 dark:border-slate-600;
}
ul,
ol,
dl {
@apply mb-2 list-disc list-outside leading-[1.65];
}
// Form elements
label {
@apply text-slate-800 dark:text-slate-200 block m-0 leading-7 text-sm font-medium;
&.error {
input {
@apply mb-1;
}
}
}
.input-wrap,
.help-text {
@apply text-slate-800 dark:text-slate-100 text-sm font-medium;
.help-text {
@apply font-normal text-slate-600 dark:text-slate-400;
}
}
// Focus outline removal
.button,
textarea,
input:focus {
outline: none;
}
// Inputs
input[type='text'],
input[type='number'],
input[type='password'],
input[type='date'],
input[type='email'],
input[type='url'] {
@apply block box-border w-full transition-colors focus:border-woot-500 dark:focus:border-woot-600 duration-[0.25s] ease-[ease-in-out] h-10 appearance-none mx-0 mt-0 mb-4 p-2 rounded-md text-base font-normal bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-solid 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 cursor-not-allowed;
}
}
input[type='file'] {
@apply bg-white dark:bg-slate-800 leading-[1.15] mb-4;
}
// Select
select {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28110, 111, 115%29'></polygon></svg>");
background-position: right -1rem center;
background-size: 9px 6px;
@apply h-10 mx-0 mt-0 mb-4 bg-origin-content focus-visible:outline-none bg-no-repeat py-2 pr-6 pl-2 rounded-md w-full text-base font-normal appearance-none transition-colors focus:border-woot-500 dark:focus:border-woot-600 duration-[0.25s] ease-[ease-in-out] bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-solid border-slate-200 dark:border-slate-600;
}
// Textarea
textarea {
@apply block box-border w-full transition-colors focus:border-woot-500 dark:focus:border-woot-600 duration-[0.25s] ease-[ease-in-out] h-16 appearance-none mx-0 mt-0 mb-4 p-2 rounded-md text-base font-normal bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-solid 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 cursor-not-allowed;
}
}
// Error handling
.has-multi-select-error {
div.multiselect {
@apply mb-1;
}
}
.error {
input,
input:not([type]),
textarea,
select,
.multiselect > .multiselect__tags,
.multiselect:not(.no-margin) {
@apply border border-solid border-red-400 dark:border-red-400 mb-1;
}
.message {
@apply text-red-400 dark:text-red-400 block text-sm mb-2.5 w-full;
}
}
.input-group.small {
input {
@apply text-sm h-8;
}
.error {
@apply border-red-400 dark:border-red-400;
}
}
// Code styling
code {
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
'"Liberation Mono"', '"Courier New"', 'monospace';
@apply text-xs border-0;
&.hljs {
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-50 rounded-lg p-5;
.hljs-number,
.hljs-string {
@apply text-red-800 dark:text-red-400;
}
}
}
@@ -1,8 +1,40 @@
// scss-lint:disable SpaceAfterPropertyColon
// scss-lint:disable MergeableSelector
button {
font-family: inherit;
transition:
background-color 0.25s ease-out,
color 0.25s ease-out;
@apply inline-block items-center mb-0 text-center align-middle cursor-pointer text-sm mt-0 mx-0 py-1 px-2.5 border border-solid border-transparent dark:border-transparent rounded-[0.3125rem];
&:disabled,
&.disabled {
@apply opacity-40 cursor-not-allowed;
}
}
.button-group {
@apply mb-0 flex flex-nowrap items-stretch;
.button {
flex: 0 0 auto;
@apply m-0 text-sm rounded-none first:rounded-tl-[0.3125rem] first:rounded-bl-[0.3125rem] last:rounded-tr-[0.3125rem] last:rounded-br-[0.3125rem] rtl:space-x-reverse;
}
.button--only-icon {
@apply w-10 justify-center pl-0 pr-0;
}
}
.back-button {
@apply m-0;
}
.button {
@apply items-center inline-flex h-10 mb-0 gap-2;
@apply items-center bg-woot-500 dark:bg-woot-500 px-2.5 text-white dark:text-white inline-flex h-10 mb-0 gap-2 font-medium;
.button__content {
@apply w-full;
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis;
img,
svg {
@@ -10,12 +42,61 @@
}
}
&:hover {
@apply bg-woot-600 dark:bg-woot-600;
}
&:disabled,
&.disabled {
@apply opacity-40 cursor-not-allowed;
}
&.success {
@apply bg-[#44ce4b] dark:bg-[#44ce4b] text-white dark:text-white;
}
&.secondary {
@apply bg-slate-700 dark:bg-slate-600 text-white dark:text-white;
}
&.primary {
@apply bg-woot-500 dark:bg-woot-500 text-white dark:text-white;
}
&.clear {
@apply text-woot-500 dark:text-woot-500 bg-transparent dark:bg-transparent;
}
&.alert {
@apply bg-red-500 dark:bg-red-500 text-white dark:text-white;
&.clear {
@apply bg-transparent dark:bg-transparent;
}
}
&.warning {
@apply bg-[#ffc532] dark:bg-[#ffc532] text-white dark:text-white;
&.clear {
@apply bg-transparent dark:bg-transparent;
}
}
&.tiny {
@apply h-6 text-[10px];
}
&.small {
@apply h-8 text-xs;
}
.spinner {
@apply px-2 py-0;
}
// @TODDO - Remove after moving all buttons to woot-button
.icon + .button__content {
.icon+.button__content {
@apply w-auto;
}
@@ -34,7 +115,7 @@
}
&.hollow {
@apply border border-woot-500 dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900;
@apply border border-woot-500 bg-transparent dark:bg-transparent dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900;
&.secondary {
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
@@ -1 +0,0 @@
// File to be removed
@@ -1,16 +0,0 @@
@keyframes left-shift-animation {
0%,
100% {
transform: translateX(0);
}
50% {
transform: translateX(1px);
}
}
.conversation {
&.active {
animation: left-shift-animation 0.25s $swift-ease-out-function;
}
}
@@ -79,7 +79,7 @@
@apply rounded-r-lg rounded-l mr-auto break-words;
&:not(.is-unsupported) {
@apply border border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-700 text-black-900 dark:text-slate-50
@apply border border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-700 text-black-900 dark:text-slate-50;
}
&.is-image {
@@ -91,7 +91,7 @@
}
.file {
.text-block-title {
.attachment-name {
@apply text-slate-700 dark:text-woot-300;
}
@@ -222,20 +222,6 @@
@apply flex relative flex-col;
}
.typing-indicator-wrap {
@apply items-center flex h-0 absolute w-full -top-8;
.typing-indicator {
@include elegant-card;
@include round-corner;
@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 {
@apply ml-2 w-6;
}
}
}
.left .bubble .text-content {
h1,
h2,
@@ -1,78 +0,0 @@
// 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'],
input[type='datetime'],
input[type='datetime-local'],
input[type='email'],
input[type='month'],
input[type='number'],
input[type='password'],
input[type='search'],
input[type='tel'],
input[type='text'],
input[type='time'],
input[type='url'],
input[type='week'],
input:not([type]),
textarea,
select,
.multiselect > .multiselect__tags {
@apply border border-solid border-red-400 dark:border-red-400;
}
.message {
@apply text-red-400 dark:text-red-400 block text-sm mb-2.5 w-full;
}
}
.button,
textarea,
input {
&:focus {
outline: none;
}
}
.input-wrap {
@apply text-slate-800 dark:text-slate-100 text-sm font-medium;
}
.help-text {
@apply font-normal text-slate-600 dark:text-slate-400;
}
.input-group.small {
input {
@apply text-sm h-8;
}
.error {
@apply border-red-400 dark:border-red-400;
}
}
@@ -1,66 +0,0 @@
.auth-wrap {
width: 100%;
}
// Outside login wrapper
.login {
@include full-height;
overflow-y: auto;
padding-top: $space-larger * 1.2;
.login__hero {
margin-bottom: $space-larger;
.hero__logo {
width: 180px;
}
.hero__title {
font-weight: $font-weight-light;
margin-top: $space-larger;
}
.hero__sub {
color: $medium-gray;
font-size: $font-size-medium;
}
}
// Login box
.login-box {
@include background-white;
@include border-normal;
@include elegant-card;
border-radius: $space-smaller;
padding: $space-large;
label {
color: $color-gray;
font-size: $font-size-default;
input {
font-size: $font-size-default;
height: $space-larger;
padding: $space-slab;
}
.error {
font-size: $font-size-small;
}
}
.button {
height: $space-larger;
}
}
.sigin__footer {
font-size: $font-size-default;
padding: $space-medium;
> a {
font-weight: $font-weight-bold;
}
}
}
@@ -1,75 +0,0 @@
.modal-mask {
// @include flex;
// @include flex-align(center, middle);
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
}
.page-top-bar {
@apply px-8 pt-9 pb-0;
img {
@apply max-h-[3.75rem];
}
}
.modal-container {
@apply shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem];
&.medium {
@apply max-w-[80%] w-[56.25rem];
}
.content-box {
@apply h-auto p-0;
}
h2 {
@apply text-slate-800 dark:text-slate-100 text-lg font-semibold;
}
p {
@apply text-sm m-0 p-0 text-slate-600 mt-2 text-sm dark:text-slate-300;
}
.content {
@apply p-8;
}
form,
.modal-content {
@apply pt-4 pb-8 px-8 self-center;
a {
@apply p-4;
}
}
.modal-footer {
// @include flex;
// @include flex-align($x: flex-end, $y: middle);
@apply flex justify-end items-center py-2 px-0 gap-2;
&.justify-content-end {
@apply justify-end;
}
}
.delete-item {
@apply p-8;
button {
@apply m-0;
}
}
}
.modal-enter,
.modal-leave {
@apply opacity-0;
}
.modal-enter .modal-container,
.modal-leave .modal-container {
transform: scale(1.1);
// @apply transform scale-110;
}
@@ -1,60 +0,0 @@
.reply-box {
transition: box-shadow 0.35s $swift-ease-out-function,
height 2s $swift-ease-out-function;
&.is-focused {
box-shadow: var(--shadow);
}
.reply-box__top {
.icon {
color: $medium-gray;
cursor: pointer;
font-size: $font-size-medium;
margin-right: $space-small;
&.active {
color: $color-woot;
}
}
.attachment {
cursor: pointer;
margin-right: $space-one;
padding: 0 $space-small;
}
.video-js {
background: transparent;
// Override min-height : 50px in foundation
//
max-height: $space-mega * 2.4;
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
> textarea {
@include ghost-input();
background: transparent;
margin: 0;
max-height: $space-mega * 2.4;
// Override min-height : 50px in foundation
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
}
&.is-private {
@apply bg-yellow-100 dark:bg-yellow-800;
.reply-box__top {
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
}
}
@@ -1,60 +0,0 @@
.report-card {
@include custom-border-top(3px, transparent);
cursor: pointer;
margin: 0;
padding: var(--space-normal);
&.active {
@include custom-border-top(3px, var(--color-woot));
@include background-white;
.heading,
.metric {
color: var(--color-woot);
}
}
.heading {
align-items: center;
color: var(--color-heading);
display: flex;
font-size: var(--font-size-small);
font-weight: var(--font-weight-bold);
margin: 0;
}
.info-icon {
color: var(--b-400);
margin-left: var(--space-micro);
}
.metric-wrap {
align-items: center;
display: flex;
}
.metric {
font-size: var(--font-size-big);
font-weight: var(--font-weight-feather);
margin-top: var(--space-smaller);
}
.metric-trend {
font-size: var(--font-size-small);
margin: 0 var(--space-small);
}
.metric-up {
color: $success-color;
}
.metric-down {
color: $alert-color;
}
.desc {
font-size: var(--font-size-small);
margin: 0;
text-transform: capitalize;
}
}
@@ -1,29 +0,0 @@
.reports-option__rounded--item {
border-radius: 100%;
height: var(--space-two);
width: var(--space-two);
}
.reports-option__item {
flex-shrink: 0;
margin-right: var(--space-small);
}
.reports-option__label--swatch {
border: 1px solid var(--color-border);
}
.reports-option__wrap {
align-items: center;
display: flex;
}
.reports-option__title {
margin: 0 var(--space-small);
}
.switch {
margin-bottom: var(--space-zero);
margin-left: var(--space-small);
}
@@ -1,18 +0,0 @@
.search {
@include flex;
@include flex-align($x: left, $y: middle);
@include flex-shrink;
padding: $space-one $space-normal;
transition: all 0.3s var(--ease-in-out-quad);
> .icon {
color: $medium-gray;
font-size: $font-size-medium;
}
> input {
@include ghost-input();
margin: 0;
}
}
@@ -1,78 +0,0 @@
.side-menu {
i {
margin-right: var(--space-smaller);
min-width: var(--space-two);
}
}
.sidebar {
z-index: 1024 - 1;
//logo
.logo {
img {
max-height: 108px;
padding: $woot-logo-padding;
}
}
.nested {
a {
font-size: var(--font-size-small);
margin-bottom: var(--space-micro);
margin-top: var(--space-micro);
.inbox-icon {
display: inline-block;
margin-right: var(--space-micro);
min-width: var(--space-normal);
text-align: center;
}
}
}
}
// bottom-nav
.bottom-nav {
@include flex;
@include space-between-column;
@include border-normal-top;
flex-direction: column;
padding: var(--space-one) var(--space-normal) var(--space-one)
var(--space-one);
position: relative;
&:hover {
background: var(--color-background-light);
}
.dropdown-pane {
bottom: 3.75rem;
display: block;
visibility: visible;
width: fit-content;
}
.active {
border-bottom: 2px solid $medium-gray;
}
}
.hamburger--menu {
cursor: pointer;
display: block;
margin-right: var(--space-normal);
}
.header--icon {
display: block;
margin: 0 var(--space-small) 0 var(--space-smaller);
@media screen and (max-width: 1200px) {
display: none;
}
}
.header-title {
margin: 0 var(--space-small);
}
@@ -1,45 +0,0 @@
.ui-snackbar-container {
left: 0;
margin: 0 auto;
max-width: 25rem;
overflow: hidden;
position: absolute;
right: 0;
text-align: center;
top: $space-normal;
z-index: 9999;
}
.ui-snackbar {
@include shadow;
background-color: $woot-snackbar-bg;
border-radius: $space-smaller;
display: inline-flex;
margin-bottom: $space-small;
max-width: 25rem;
min-height: 1.875rem;
min-width: 15rem;
padding: $space-slab $space-medium;
text-align: left;
}
.ui-snackbar-text {
color: $color-white;
font-size: $font-size-small;
font-weight: $font-weight-medium;
}
.ui-snackbar-action {
margin-left: auto;
padding-left: 1.875rem;
button {
background: none;
border: 0;
color: $woot-snackbar-button;
font-size: $font-size-small;
margin: 0;
padding: 0;
text-transform: uppercase;
}
}
@@ -1 +0,0 @@
// To be removed
@@ -1,46 +0,0 @@
.status-bar {
@include flex;
@include flex-align($x: center, $y: middle);
background: lighten($warning-color, 36%);
flex-direction: column;
margin: 0;
padding: $space-normal $space-smaller;
.message {
font-weight: $font-weight-medium;
margin-bottom: $zero;
}
.button {
margin: $space-smaller $zero $zero;
padding: $space-small $space-normal;
}
&.danger {
background: lighten($alert-color, 30%);
.button {
// Default and disabled states
&,
&.disabled,
&[disabled],
&.disabled:hover,
&[disabled]:hover,
&.disabled:focus,
&[disabled]:focus {
background-color: $alert-color;
color: $color-white;
}
&:hover,
&:focus {
background-color: darken($alert-color, 7%);
color: $color-white;
}
}
}
&.warning {
background: lighten($warning-color, 36%);
}
}
@@ -7,7 +7,7 @@
}
.tabs {
@apply border-r-0 border-l-0 border-t-0 flex min-w-[6.25rem] py-0 px-4;
@apply border-r-0 border-l-0 border-t-0 flex min-w-[6.25rem] py-0 px-4 list-none mb-0;
}
.tabs--with-scroll {
@@ -31,7 +31,7 @@
}
.tabs-title {
@apply flex-shrink-0 my-0 mx-2 ;
@apply flex-shrink-0 my-0 mx-2;
.badge {
@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;
@@ -53,7 +53,7 @@
}
a {
@apply flex items-center flex-row border-b border-transparent text-slate-500 dark:text-slate-200 text-sm top-[1px] relative;
@apply flex items-center flex-row border-b py-2.5 select-none cursor-pointer border-transparent text-slate-500 dark:text-slate-200 text-sm top-[1px] relative;
transition: border-color 0.15s $swift-ease-out-function;
}
@@ -1,9 +1,9 @@
table {
@apply border-spacing-0 text-sm;
@apply border-spacing-0 text-sm w-full;
thead {
th {
@apply font-semibold tracking-[1px] text-left uppercase text-slate-900 dark:text-slate-200;
@apply font-semibold tracking-[1px] text-left px-2.5 uppercase text-slate-900 dark:text-slate-200;
}
}
@@ -1,7 +1,7 @@
<template>
<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"
class="flex items-center select-none w-full rounded-none 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">
@@ -1,10 +1,12 @@
<template>
<button
class="sm:w-[50%] md:w-[34%] lg:w-[25%] channel"
class="sm:w-[50%] md:w-1/3 lg:w-1/4 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-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
@click="$emit('click')"
>
<img :src="src" :alt="title" />
<h3 class="channel__title">
<img :src="src" :alt="title" class="w-[50%] my-4 mx-auto" />
<h3
class="text-slate-800 dark:text-slate-100 text-base text-center capitalize"
>
{{ title }}
</h3>
</button>
@@ -31,32 +33,8 @@ export default {
filter: grayscale(100%);
}
&.channel:hover {
&:hover {
@apply border-transparent shadow-none cursor-not-allowed;
}
}
.channel {
@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 {
@apply border-woot-500 dark:border-woot-500 shadow-md z-50;
}
&.disabled {
@apply opacity-60;
}
img {
@apply w-[50%] my-4 mx-auto;
}
.channel__title {
@apply text-slate-800 dark:text-slate-100 text-base text-center capitalize;
}
p {
@apply text-slate-600 dark:text-slate-300 w-full text-sm;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div
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="conversations-list-wrap flex-basis-clamp flex-shrink-0 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,
@@ -16,7 +16,7 @@
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl break-words overflow-hidden whitespace-nowrap text-ellipsis text-black-900 dark:text-slate-100 mb-0"
class="text-xl break-words overflow-hidden whitespace-nowrap font-medium text-ellipsis text-black-900 dark:text-slate-100 mb-0"
:title="pageTitle"
>
{{ pageTitle }}
@@ -143,7 +143,10 @@
<div v-if="chatListLoading" class="text-center">
<span class="spinner mt-4 mb-4" />
</div>
<p v-if="showEndOfListMessage" class="text-center text-muted p-4">
<p
v-if="showEndOfListMessage"
class="text-center text-slate-400 dark:text-slate-300 p-4"
>
{{ $t('CHAT_LIST.EOF') }}
</p>
<intersection-observer
@@ -1,7 +1,7 @@
<template>
<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">
<h4 class="text-sm flex items-center m-0 w-full error">
<div v-if="isAttributeTypeCheckbox" class="checkbox-wrap">
<input
v-model="editedValue"
@@ -12,7 +12,7 @@
</div>
<div class="flex items-center justify-between w-full">
<span
class="attribute-name"
class="attribute-name w-full text-slate-800 dark:text-slate-100 font-medium text-sm mb-0"
:class="{ error: $v.editedValue.$error }"
>
{{ label }}
@@ -32,19 +32,24 @@
</div>
<div v-if="notAttributeTypeCheckboxAndList">
<div v-show="isEditing">
<div class="input-group small">
<div class="mb-2 w-full flex items-center">
<input
ref="inputfield"
v-model="editedValue"
:type="inputType"
class="input-group-field"
class="!h-8 ltr:rounded-r-none rtl:rounded-l-none !mb-0 !text-sm"
autofocus="true"
:class="{ error: $v.editedValue.$error }"
@blur="$v.editedValue.$touch"
@keyup.enter="onUpdate"
/>
<div class="input-group-button">
<woot-button size="small" icon="checkmark" @click="onUpdate" />
<div>
<woot-button
size="small"
icon="checkmark"
class="rounded-l-none rtl:rounded-r-none"
@click="onUpdate"
/>
</div>
</div>
<span
@@ -306,7 +311,6 @@ export default {
@apply my-0 mr-2 ml-0;
}
.attribute-name {
@apply w-full text-slate-800 dark:text-slate-100;
&.error {
@apply text-red-400 dark:text-red-500;
}
+36 -19
View File
@@ -62,9 +62,9 @@ export default {
computed: {
modalContainerClassName() {
let className =
'modal-container bg-white dark:bg-slate-800 skip-context-menu';
'modal-container rtl:text-right shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem] bg-white dark:bg-slate-800 skip-context-menu';
if (this.fullWidth) {
return `${className} modal-container--full-width`;
return `${className} items-center rounded-none flex h-full justify-center w-full`;
}
return `${className} ${this.size}`;
@@ -109,26 +109,43 @@ export default {
};
</script>
<style scoped lang="scss">
.modal-container--full-width {
align-items: center;
border-radius: 0;
display: flex;
height: 100%;
justify-content: center;
width: 100%;
}
.modal-mask.right-aligned {
justify-content: flex-end;
<style lang="scss">
.modal-mask {
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
.modal-container {
border-radius: 0;
height: 100%;
width: 30rem;
&.medium {
@apply max-w-[80%] w-[56.25rem];
}
// .content-box {
// @apply h-auto p-0;
// }
.content {
@apply p-8;
}
form,
.modal-content {
@apply pt-4 pb-8 px-8 self-center;
a {
@apply p-4;
}
}
}
}
.modal-big {
width: 60%;
@apply w-full;
}
.modal-mask.right-aligned {
@apply justify-end;
.modal-container {
@apply rounded-none h-full w-[30rem];
}
}
.modal-enter,
.modal-leave {
@apply opacity-0;
}
.modal-enter .modal-container,
.modal-leave .modal-container {
transform: scale(1.1);
}
</style>
@@ -3,7 +3,7 @@
<img v-if="headerImage" :src="headerImage" alt="No image" />
<h2
ref="modalHeaderTitle"
class="text-slate-800 text-lg dark:text-slate-100"
class="text-slate-800 text-lg font-semibold dark:text-slate-50"
>
{{ headerTitle }}
</h2>
@@ -1,19 +1,21 @@
<template>
<div
class="ml-0 mr-0 flex pt-0 pr-4 pb-4 pl-0"
class="ml-0 mr-0 flex py-8 w-full xl:w-3/4 flex-col xl:flex-row"
:class="{
'pt-4 border-b border-solid border-slate-50 dark:border-slate-700/30':
'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">
<div class="w-full xl:w-1/4 min-w-0 xl: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="text-sm mb-2">
<p
class="text-sm mb-2 text-slate-700 dark:text-slate-300 leading-5 tracking-normal mt-2"
>
<slot v-if="subTitle" name="subTitle">
{{ subTitle }}
</slot>
@@ -23,7 +25,7 @@
{{ note }}
</p>
</div>
<div class="w-[50%] min-w-0 max-w-[50%]">
<div class="w-full xl:w-1/2 min-w-0 xl:max-w-[50%]">
<slot />
</div>
</div>
@@ -1,11 +1,17 @@
<template>
<div>
<div class="ui-snackbar">
<div class="ui-snackbar-text">
<div
class="shadow-sm bg-slate-800 dark:bg-slate-700 rounded-[4px] items-center gap-3 inline-flex mb-2 max-w-[25rem] min-h-[1.875rem] min-w-[15rem] px-6 py-3 text-left"
>
<div class="text-white dark:text-white text-sm font-medium">
{{ message }}
</div>
<div v-if="action" class="ui-snackbar-action">
<router-link v-if="action.type == 'link'" :to="action.to">
<div v-if="action">
<router-link
v-if="action.type == 'link'"
:to="action.to"
class="text-woot-500 dark:text-woot-500 cursor-pointer font-medium hover:text-woot-600 dark:hover:text-woot-600 select-none"
>
{{ action.message }}
</router-link>
</div>

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