Compare commits

...
Author SHA1 Message Date
Sojan 7520ca7a99 Merge branch 'release/4.0.2'
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Waiting to run
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Waiting to run
Publish Chatwoot EE docker images / merge (push) Blocked by required conditions
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Waiting to run
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Waiting to run
Publish Chatwoot CE docker images / merge (push) Blocked by required conditions
2025-02-21 17:04:31 -08:00
Sojan 83967735d3 Bump version to 4.0.2 2025-02-21 17:04:11 -08:00
19cfd4be4c chore: Clean up the feature presentation in super admin (#10949)
- improve styling of the feature toggle displayed in super admin

<img width="1540" alt="Screenshot 2025-02-21 at 4 47 58 PM"
src="https://github.com/user-attachments/assets/c111ff0d-c2ce-4609-832b-0ea631c97471"
/>

<img width="1451" alt="Screenshot 2025-02-21 at 4 48 11 PM"
src="https://github.com/user-attachments/assets/fe5df4ae-984b-4f9d-baba-9555935fbd55"
/>

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
2025-02-21 17:01:36 -08:00
161024db9d feat: Add visibility checks for installation types (#10773)
This pull request includes multiple changes to the sidebar and route
metas to configure visibility of features on the dashboard.

Here's a summary of the changes

1. Added `installationTypes`, field to routes `meta`, this works along
side `permissions` and `featureFlags`
This allows us to decide weather a particular feature is accessible on a
particular type. For instance, the Billing pages should only be
available on Cloud
2. Updated `usePolicy` and `policy.vue` to use the new
`installationTypes` config
3. Updated Sidebar related components to remove `showOnlyOnCloud` to use
the new policy updates.

Testing the PR

Here's the matrix of cases:
https://docs.google.com/spreadsheets/d/15AAJntJZoyudaby77BOnRcC4435FGuT7PXbUXoTyU50/edit?usp=sharing

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
2025-02-21 14:48:31 -08:00
a7e73de8d4 feat: Don't hide thumbnail on hover (#10935)
### Old behavior


https://github.com/user-attachments/assets/5d7524c1-b1ca-454f-8e89-843ca25d017e

### New behavior


https://github.com/user-attachments/assets/49814190-d6b4-4553-bced-3987be3583e2

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2025-02-21 13:50:30 -08:00
Sivin VargheseandGitHub ca02b442dd fix: Reports chart by removing y-axis numeric labels (#10941)
# Pull Request Template

## Description

This PR fixes showing y-axis numeric labels in reports charts. It occurs
after merging this PR. https://github.com/chatwoot/chatwoot/pull/10938

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

#### **Screenshots**

#### **Before**
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/40299ba8-6f6b-43d5-b71a-5af22c238e2d"
/>


#### **After**
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/72ee5729-9789-43c4-a85f-8037f647c7df"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-21 13:48:43 -08:00
Sivin VargheseandGitHub 4efeb4eda2 fix: Incorrect translation in contact merge modal and resolve z-index issue (#10943)
# Pull Request Template

## Description

This PR includes fix for,

**1**. Incorrect error message translation for search in contact merge
modal.
<img width="395" alt="image"
src="https://github.com/user-attachments/assets/e6592bcc-4656-4a6e-83a9-7124e2c4732a"
/>


**2**. Z-index issue in chat screen
<img width="395" alt="image"
src="https://github.com/user-attachments/assets/387a2b47-ea85-49c7-832b-8bc1a4f0ec3f"
/>


## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Screenshots**
**1.** 
<img width="395" alt="image"
src="https://github.com/user-attachments/assets/a1f1e36d-0801-4e7b-9ee9-c72728933332"
/>
**2.** 
<img width="395" alt="image"
src="https://github.com/user-attachments/assets/60a0ddc1-eb8f-4dbc-9028-245e6bb64c33"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-20 22:01:36 -08:00
Shivam MishraandGitHub c88447c11f feat: invalidate cache after inbox members or team members update (#10869)
At the moment, when updating the inbox members, or team members the
account cache used for IndexedDB is not invalidated. This can cause
inconsistencies in the UI. This PR fixes this by adding explicit
invalidation after performing the member changes

### Summary of changes

1. Added a new method `add_members` and `remove_members` to both `team`
and `inbox` models. The change was necessary for two reasons
- Since the individual `add_member` and `remove_member` is called in a
loop, it's wasteful to run the cache invalidation in the method.
- Moving the account cache invalidation call in the controller pollutes
the controller business logic
2. Updated tests across the board

### More improvements

We can make a concern called `Memberable` with usage like
`memberable_with :inbox_members`, that can encapsulate the functionality

---

Related: https://github.com/chatwoot/chatwoot/issues/10578
2025-02-20 21:28:38 -08:00
Shivam MishraandGitHub 27f7e0921e feat: hide tokens and password on contact inbox payloads (#10888)
Contact Inbox object contained unnecessary attributes which will be removed under this PR.
2025-02-20 21:22:12 -08:00
PranavandGitHub f3b8777ebf fix: Fix issues with leave room button (#10942)
Fixes
https://linear.app/chatwoot/issue/CW-4069/typeerror-cannot-set-properties-of-undefined-setting-dyteauthtoken
2025-02-20 20:27:17 -08:00
PranavandGitHub 73f6b888cd fix: Fix issues in bubble design (#10940)
Just making it easier to test and merge
https://github.com/chatwoot/chatwoot/pull/10796.

This PR does the following: 

- Removes the change on br + br condition. 
- Support 1x, 1.5x, 2x playbacks
- Add a hover on the agent avatar
2025-02-21 09:51:54 +05:30
Sivin VargheseandGitHub b8fc921f9a fix: Convert seconds based metric tooltips to readable format (#10938)
# Pull Request Template

## Description

Fixes
https://linear.app/chatwoot/issue/CW-4064/convert-average-metric-tooltips-from-seconds-to-readable-time-format

#### **Cause**
Chart tooltip configuration was using outdated Chart.js structure,
causing the time formatting function to not be applied correctly to
tooltip values in time-based metrics.

#### **Solution**
Updated tooltip configuration to use correct Chart.js Vue 3 plugin
structure

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

### **Screenshots**

#### **Before**
<img width="496" alt="image"
src="https://github.com/user-attachments/assets/a70cbfe6-f179-43dc-a8f4-93951b257e81"
/>


#### **After**
<img width="496" alt="image"
src="https://github.com/user-attachments/assets/ed5d0c6c-2404-43ae-82fa-bbe5c42cecca"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-20 17:49:28 -08:00
Vishnu NarayananandGitHub 990d045cb3 fix: handle empty FIRECRAWL_KEY in captain crawl job (#10936)
- handle empty FIRECRAWL_KEY in captain crawl job
2025-02-20 12:27:37 -08:00
Shivam MishraandGitHub 7fcb0d6880 feat: validate sender before creating campaign (#10934) 2025-02-20 16:15:03 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9631b0b929 chore(deps): Bump dompurify from 3.1.6 to 3.2.4 (#10928)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.1.6 to
3.2.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.2.4</h2>
<ul>
<li>Fixed a conditional and config dependent mXSS-style <a
href="https://nsysean.github.io/posts/dompurify-323-bypass/">bypass</a>
reported by <a
href="https://github.com/nsysean"><code>@​nsysean</code></a></li>
<li>Added a new feature to allow specific hook removal, thanks <a
href="https://github.com/davecardwell"><code>@​davecardwell</code></a></li>
<li>Added <em>purify.js</em> and <em>purify.min.js</em> to exports,
thanks <a
href="https://github.com/Aetherinox"><code>@​Aetherinox</code></a></li>
<li>Added better logic in case no window object is president, thanks <a
href="https://github.com/yehuya"><code>@​yehuya</code></a></li>
<li>Updated some dependencies called out by dependabot</li>
<li>Updated license files etc to show the correct year</li>
</ul>
<h2>DOMPurify 3.2.3</h2>
<ul>
<li>Fixed two conditional sanitizer bypasses discovered by <a
href="https://github.com/parrot409"><code>@​parrot409</code></a> and <a
href="https://x.com/slonser_"><code>@​Slonser</code></a></li>
<li>Updated the attribute clobbering checks to prevent future bypasses,
thanks <a
href="https://github.com/parrot409"><code>@​parrot409</code></a></li>
</ul>
<h2>DOMPurify 3.2.2</h2>
<ul>
<li>Fixed a possible bypass in case a rather specific config for custom
elements is set, thanks <a
href="https://github.com/yaniv-git"><code>@​yaniv-git</code></a></li>
<li>Fixed several minor issues with the type definitions, thanks again
<a href="https://github.com/reduckted"><code>@​reduckted</code></a></li>
<li>Fixed a minor issue with the types reference for trusted types,
thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a></li>
<li>Fixed a minor problem with the template detection regex on some
systems, thanks <a
href="https://github.com/svdb99"><code>@​svdb99</code></a></li>
</ul>
<h2>DOMPurify 3.2.1</h2>
<ul>
<li>Fixed several minor issues with the type definitions, thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a> <a
href="https://github.com/ghiscoding"><code>@​ghiscoding</code></a> <a
href="https://github.com/asamuzaK"><code>@​asamuzaK</code></a> <a
href="https://github.com/MiniDigger"><code>@​MiniDigger</code></a></li>
<li>Fixed an issue with non-minified dist files and order of imports,
thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a></li>
</ul>
<h2>DOMPurify 3.2.0</h2>
<ul>
<li>Added type declarations, thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a> , <a
href="https://github.com/philmayfield"><code>@​philmayfield</code></a>,
<a href="https://github.com/aloisklink"><code>@​aloisklink</code></a>,
<a href="https://github.com/ssi02014"><code>@​ssi02014</code></a> and
others</li>
<li>Fixed a minor issue with the handling of hooks, thanks <a
href="https://github.com/kevin-mizu"><code>@​kevin-mizu</code></a></li>
</ul>
<h2>DOMPurify 3.1.7</h2>
<ul>
<li>Fixed an issue with comment detection and possible bypasses with
specific config settings, thanks <a
href="https://github.com/masatokinugawa"><code>@​masatokinugawa</code></a></li>
<li>Fixed several smaller typos in documentation and test &amp; build
files, thanks <a
href="https://github.com/christianhg"><code>@​christianhg</code></a></li>
<li>Added better support for Angular compiler, thanks <a
href="https://github.com/jeroen1602"><code>@​jeroen1602</code></a></li>
<li>Added several new attributes to HTML and SVG allow-list, thanks <a
href="https://github.com/Gigabyte5671"><code>@​Gigabyte5671</code></a>
and <a href="https://github.com/Rotzbua"><code>@​Rotzbua</code></a></li>
<li>Removed the <code>foreignObject</code> element from the list of HTML
entry-points, thanks <a
href="https://github.com/masatokinugawa"><code>@​masatokinugawa</code></a></li>
<li>Bumped several dependencies to be more up to date</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/ec29e65f3675077c1a78e6edcab97f797f004a2e"><code>ec29e65</code></a>
Merge pull request <a
href="https://redirect.github.com/cure53/DOMPurify/issues/1062">#1062</a>
from cure53/main</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/1c1b1838625851939d4b86436feeb3e3ccb7dbb6"><code>1c1b183</code></a>
chore: Preparing 3.2.4 release</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/d18ffcb554e0001748865da03ac75dd7829f0f02"><code>d18ffcb</code></a>
fix: Changed the template literal regex to avoid a config-dependent
bypass</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/0d64d2b12f9ecaa28899c60aba0b9ed5072c4d93"><code>0d64d2b</code></a>
Merge pull request <a
href="https://redirect.github.com/cure53/DOMPurify/issues/1060">#1060</a>
from yehuya/initializeTestImprovements</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/9ad79331566e0671788b120cc1f682f4e7f33f44"><code>9ad7933</code></a>
tests: DOMPurify custom window tests improvements</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/72760ca8ee425c3129420390d4c10afe698ff2fb"><code>72760ca</code></a>
Merge pull request <a
href="https://redirect.github.com/cure53/DOMPurify/issues/1059">#1059</a>
from yehuya/fixMissingWindowElement</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/bc72d44b2e3cec00d8ef2427869d03cb668969fa"><code>bc72d44</code></a>
Fix tests</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/363a89dd3a051b72334cd4d0c867616fdc4ee40d"><code>363a89d</code></a>
fix: handle undefined Element in DOMPurify initialization</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/f41b45df18a9666a50c1ad2662cee259230cfef4"><code>f41b45d</code></a>
Update LICENSE</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/b25bf26d674ff4db9074cef01fc51490cb1369a0"><code>b25bf26</code></a>
Update README.md</li>
<li>Additional commits viewable in <a
href="https://github.com/cure53/DOMPurify/compare/3.1.6...3.2.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.1.6&new-version=3.2.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-19 14:55:59 -08:00
PranavandGitHub 0a2fd7b1f4 feat: Allow users to see heatmap for last 30 days (#10848)
<img width="989" alt="Screenshot 2025-02-05 at 6 34 12 PM"
src="https://github.com/user-attachments/assets/ae811842-23f7-4bbc-8a42-7cbe4849d287"
/>

View heatmaps for last 30 days based on the filter.
2025-02-19 14:54:15 -08:00
11a7414dc0 feat: Upgrade Dyte apis to v2 (#10706)
# Pull Request Template

## Description

Dyte V1 API's are soon going to be deprecated, hence making sure we
update Chatwoot before that happens

Fixes #10704

## Type of change

Please delete options that are not relevant.

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

1. Open a new or existing conversation from the inbox
2. Press the video call icon on the message composer
3. Verify that the message dialog shows up with the join video call
button
4. Verify that clicking on join call does join the call

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [ ] New and existing unit tests pass locally with my changes (Unable
to run this locally)

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-02-19 14:47:48 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Sojan Jose
9a4c1e1fb9 chore(deps): Bump nokogiri from 1.18.2 to 1.18.3 (#10929)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.18.2
to 1.18.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sparklemotion/nokogiri/releases">nokogiri's
releases</a>.</em></p>
<blockquote>
<h2>v1.18.3 / 2025-02-18</h2>
<h3>Security</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated to <a
href="https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.13.6">v2.13.6</a>
to address CVE-2025-24928 and CVE-2024-56171. See <a
href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-vvfq-8hwr-qm4m">GHSA-vvfq-8hwr-qm4m</a>
for more information.</li>
</ul>
<!-- raw HTML omitted -->

<pre><code>cab20305133078a8f6b60cf96311b48319175038cc7772e5ec586ff624cb7838
nokogiri-1.18.3-aarch64-linux-gnu.gem
acb256bb3213a180b1ed84a49c06d5d4c6c1da26f33bc9681f1fece4dab09a79
nokogiri-1.18.3-aarch64-linux-musl.gem
ce088965cd424b8e752d82087dcf017069d55791f157098ed1f671d966857610
nokogiri-1.18.3-arm64-darwin.gem
37b73a55e0d1e8a058a24abb16868903e81cb4773049739c532b864f87236b1b
nokogiri-1.18.3-arm-linux-gnu.gem
09407970cd13736cf87e975fae69c13e1178bab0313d07b35580ee4dd3650793
nokogiri-1.18.3-arm-linux-musl.gem
6b9fc3b14fd0cedd21f6cad8cf565123ba7401e56b5d0aec180c23cdca28fd5a
nokogiri-1.18.3.gem
236078c5f80ffc3d49c223fa98933d970543455403f9d672ca0aa5a6178a84fe
nokogiri-1.18.3-java.gem
216be1cb454c4657fc64747e5ae32b2ab4015843183766f238e4f4a62fb1f6be
nokogiri-1.18.3-x64-mingw-ucrt.gem
d729406bb5a7b1bbe7ed3c0922336dd2c46085ed444d6de2a0a4c33950a4edea
nokogiri-1.18.3-x86_64-darwin.gem
3c7ad5cee39855ed9c746065f39b584b9fd2aaff61df02d0f85ba8d671bbe497
nokogiri-1.18.3-x86_64-linux-gnu.gem
8aaecc22c0e5f12dac613e15f9a04059c3ec859d6f98f493cc831bd88fe8e731
nokogiri-1.18.3-x86_64-linux-musl.gem
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sparklemotion/nokogiri/blob/v1.18.3/CHANGELOG.md">nokogiri's
changelog</a>.</em></p>
<blockquote>
<h2>v1.18.3 / 2025-02-18</h2>
<h3>Security</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated <a
href="https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.13.6">v2.13.6</a>
to address CVE-2025-24928 and CVE-2024-56171. Nokogiri's maintainers
believe these vulnerabilities do not affect users of Nokogiri, but we
advise upgrading at your earliest convenience anyway.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/fd3ca2e22df67fd25fd5c25c466f6896b6547cb8"><code>fd3ca2e</code></a>
version bump to v1.18.3</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/a8c526adf17716cf115e78995c815fc65f5937a2"><code>a8c526a</code></a>
dep: update libxml2 to v2.13.6 (<a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3437">#3437</a>)</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/0847cf86885ea5d25ccc9d3ff1c4c097f2891a60"><code>0847cf8</code></a>
ci: tired of waiting for gnome mirrors</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/11945c82df6fca8827c2914e585e8e3a7027dcb5"><code>11945c8</code></a>
dep: update libxml2 to v2.13.6</li>
<li>See full diff in <a
href="https://github.com/sparklemotion/nokogiri/compare/v1.18.2...v1.18.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nokogiri&package-manager=bundler&previous-version=1.18.2&new-version=1.18.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-02-19 14:37:26 -08:00
Shivam MishraandGitHub 098a6263a9 fix: logo and custom branding (#10930)
This PR has the following changes

1. Update `Logo.vue` to fetch the logo from globalConfig and fallback to
SVG if required.
2. Update the default `public/brand-assets/logo_thumbnail.svg` to use
the new colors.

### Preview


https://github.com/user-attachments/assets/505a1d05-cd05-4b86-9fe7-c05334f09b25

![CleanShot 2025-02-19 at 10 56
23@2x](https://github.com/user-attachments/assets/084064b9-5df5-4d11-9b6a-b511c6047001)
2025-02-19 14:22:25 -08:00
Muhsin KelothandGitHub 7590faaa63 fix: Join Dyte meeting URL in dashboard (#10932)
The join meeting feature was broken in the dashboard because the message
ID was missing in the meeting link. This PR fixes these issues.
2025-02-19 16:03:37 +05:30
Muhsin KelothandGitHub 031199f15a chore: Slack file upload changes (#10903)
The Slack `files.upload` API endpoint is deprecated and will stop
functioning on March 11, 2025. In this PR, we have implemented the
changes for the [new file
upload](https://api.slack.com/messaging/files#uploading_files) method.
2025-02-18 22:44:30 -08:00
8a5b007bc4 feat: update vue-letter and allow transform css (#10865)
Emails from iOS and MacOS clients can often come with size
transformation. This results in a broken layout when sanitizing with Vue
letter.

This PR updates the vue-letter to include the [recent changes made
there](https://github.com/mat-sz/vue-letter/pull/2). And adds the
`transform` and `transform-origin` properties to the allow list

Ref: https://www.caniemail.com/features/css-transform/

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-02-19 09:07:00 +05:30
69e3926db6 chore(deps): Switch html2text back to rubygems (#10911)
## Description

Hi! I've recently started helping maintain this gem as we use it heavily
in our app as well. It's been updated to work with newer versions of
nokogiri and has a few important fixes too.

## How Has This Been Tested?

Assuming you already have test coverage that would cover this.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-02-18 09:03:55 -08:00
Sivin VargheseandGitHub 8187bb8a38 fix: Wrong toast message on creation of a Live Chat campaign (#10919) 2025-02-17 22:37:08 +05:30
PranavandGitHub 4de1cd8c22 fix: Disable branding on help center if the feature is turned on (#10916)
Disable branding on help center as well if the feature is turned on the
account.
2025-02-16 17:42:02 -08:00
3b141fca28 chore: Add internal feature flags for Chatwoot Cloud (#10902)
This PR introduces internal feature flags for testing purposes. These
flags will not be displayed on regular instances to prevent customer
confusion.

Additionally, a new feature flag, `contact_chatwoot_support_team`, has
been added for Chatwoot Cloud. This flag disables contact support for
third-party onboarded accounts, as support will be handled by the
original affiliate team.

Co-authored-by: Pranav <pranav@chatwoot.com>
2025-02-15 16:21:46 -08:00
PranavandGitHub ccf890d855 fix: Handle JSON requests in DashboardController (#10910)
This is the error that is triggering a P0 incident in Chatwoot. 

```
DashboardController#index is missing a template for this request format and variant. request.formats: ["application/json"] request.variant: []
```

The user is calling `/app/accounts/api/v1/accounts/<account-id>/inboxes`. The URL is wrong, the requests are routed to dashboard controller as it starts with `/app/accounts`. The dashboard controller is not handling JSON requests and it creates errors. There are 312k errors over the last 2 years. Close to 50k during last 3 days.

This fix would return not_acceptable response to the attempts.
2025-02-15 09:32:50 -08:00
PranavandGitHub 94baba1d15 fix: Use textContent as fallback for htmlContent instead of content.value (#10901)
Some emails contain HTML content within the text part, but our content
parser currently strips HTML automatically, which needs a proper fix. In
the new UI, the fallback for HTML content was set to the parsed content,
which may omit HTML tags, leading to issues like missing inline
attachments.

This PR updates the fallback mechanism to use the text content instead
of the parsed content, ensuring HTML elements are preserved.

Fixes
https://linear.app/chatwoot/issue/CW-4046/text-content-is-not-respected-in-emails
2025-02-13 19:23:04 -08:00
Sivin VargheseandGitHub f112e500e1 feat: Add the ability to block/unblock contact via contact details page (#10899) 2025-02-13 14:59:35 -08:00
Sivin VargheseandGitHub 43753bc74a fix: UI issue with pt-Br locale (#10897)
Fixes https://linear.app/chatwoot/issue/CW-4041/issues-with-pt-br-locale
2025-02-13 12:42:28 -08:00
Sivin VargheseandGitHub 5cb8645edb feat: Ability to rearrange macros in sidebar (#10879) 2025-02-13 17:26:05 +05:30
PranavandGitHub 6e48e73e73 fix: Initialize SDK along with emitter registration (#10896)
Initialize the SDK along with the emitter registration to ensure
accurate data reporting for analytics events.
2025-02-12 22:14:04 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
46c0198b6a chore(deps): Bump rack from 2.2.10 to 2.2.11 (#10894)
Bumps [rack](https://github.com/rack/rack) from 2.2.10 to 2.2.11.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rack/rack/blob/main/CHANGELOG.md">rack's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.
For info on how to format all future additions to this file please
reference <a href="https://keepachangelog.com/en/1.0.0/">Keep A
Changelog</a>.</p>
<h2>Unreleased</h2>
<h3>Added</h3>
<ul>
<li>Introduce <code>Rack::VERSION</code> constant. (<a
href="https://redirect.github.com/rack/rack/pull/2199">#2199</a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
<li>ISO-2022-JP encoded parts within MIME Multipart sections of an HTTP
request body will now be converted to UTF-8. (<a
href="https://redirect.github.com/rack/rack/pull/2245">#2245</a>, [<a
href="https://github.com/nappa"><code>@​nappa</code></a>])</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Invalid cookie keys will now raise an error. (<a
href="https://redirect.github.com/rack/rack/pull/2193">#2193</a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
<li><code>Rack::MediaType#params</code> now handles empty strings. (<a
href="https://redirect.github.com/rack/rack/pull/2229">#2229</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
</ul>
<h3>Deprecated</h3>
<ul>
<li><code>Rack::Auth::AbstractRequest#request</code> is deprecated
without replacement. (<a
href="https://redirect.github.com/rack/rack/pull/2229">#2229</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
<li><code>Rack::Request#parse_multipart</code> (private method designed
to be overridden in subclasses) is deprecated without replacement. (<a
href="https://redirect.github.com/rack/rack/pull/2229">#2229</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
</ul>
<h3>Removed</h3>
<ul>
<li><code>Rack::Request#values_at</code> is removed. (<a
href="https://redirect.github.com/rack/rack/pull/2200">#2200</a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
<li><code>Rack::Logger</code> is removed with no replacement. (<a
href="https://redirect.github.com/rack/rack/pull/2196">#2196</a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
<li>Automatic cache invalidation in
<code>Rack::Request#{GET,POST}</code> has been removed. (<a
href="https://redirect.github.com/rack/rack/pull/2230">#2230</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><code>Rack::RewindableInput::Middleware</code> no longer wraps a nil
input. (<a
href="https://redirect.github.com/rack/rack/pull/2259">#2259</a>, <a
href="https://github.com/tt"><code>@​tt</code></a>)</li>
</ul>
<h2>[3.1.9] - 2025-01-31</h2>
<h3>Fixed</h3>
<ul>
<li><code>Rack::MediaType#params</code> now handles parameters without
values. (<a
href="https://redirect.github.com/rack/rack/pull/2263">#2263</a>, <a
href="https://github.com/AllyMarthaJ"><code>@​AllyMarthaJ</code></a>)</li>
</ul>
<h2>[3.1.8] - 2024-10-14</h2>
<h3>Fixed</h3>
<ul>
<li>Resolve deprecation warnings about uri <code>DEFAULT_PARSER</code>.
(<a href="https://redirect.github.com/rack/rack/pull/2249">#2249</a>,
[<a
href="https://github.com/earlopain"><code>@​earlopain</code></a>])</li>
</ul>
<h2>[3.1.7] - 2024-07-11</h2>
<h3>Fixed</h3>
<ul>
<li>Do not remove escaped opening/closing quotes for content-disposition
filenames. (<a
href="https://redirect.github.com/rack/rack/pull/2229">#2229</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
<li>Fix encoding setting for non-binary IO-like objects in
MockRequest#env_for. (<a
href="https://redirect.github.com/rack/rack/pull/2227">#2227</a>, [<a
href="https://github.com/jeremyevans"><code>@​jeremyevans</code></a>])</li>
<li><code>Rack::Response</code> should not generate invalid
<code>content-length</code> header. (<a
href="https://redirect.github.com/rack/rack/pull/2219">#2219</a>, [<a
href="https://github.com/ioquatix"><code>@​ioquatix</code></a>])</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/rack/rack/commit/aa5a0f532aac7a57e4bc7e857eca1c38229f7b30"><code>aa5a0f5</code></a>
Bump patch version.</li>
<li><a
href="https://github.com/rack/rack/commit/f8b41c1dba33ec96b886e57505c149c36d56662b"><code>f8b41c1</code></a>
Escape non-printable characters when logging.</li>
<li>See full diff in <a
href="https://github.com/rack/rack/compare/v2.2.10...v2.2.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rack&package-manager=bundler&previous-version=2.2.10&new-version=2.2.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-12 18:22:48 -08:00
4397ee7d3b chore: Update translations, add pnpm sync:i18n command (#10893)
Added a command to sync files in the locale/*/ folder. Run `pnpm
sync:i18n` would copy index.js in `dashboard/locale/en` to every other
folder `dashboard/locale/*/`

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
2025-02-12 17:43:46 -08:00
PranavandGitHub 3af7c3b2e7 fix: Move auto resolution message text content to i18n file (#10881)
Move the text content to i18n file.
2025-02-12 16:51:35 -08:00
1af726e05e chore: Update translations (#10890)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2025-02-12 09:33:19 -08:00
Vishnu NarayananandGitHub 1da0474ed1 chore: add gh action to test docker builds against PRs (#10892)
- Add GitHub action to test docker builds for internal/external PR's
- This PR builds the image on both amd64 and arm64 runners for every PR
- This also reduces the build time by 70% (50mins to 15mins)
2025-02-12 17:16:33 +05:30
Vishnu NarayananandGitHub adff853086 feat: docker arm64 builds for ee edition (#10891)
- Publish arm64 docker images for chatwoot ee edition
- Switch to Github actions for `docker` build/publish from docker hub
due to lack of arm64 support


**Note: CE edition arm64 images are already available via
https://github.com/chatwoot/chatwoot/pull/10789**
2025-02-12 16:28:53 +05:30
Sivin VargheseandGitHub 5adc5c7de6 feat: Adds multiple attachment support for Telegram channel (#10883) 2025-02-12 14:43:58 +05:30
4a088e0663 chore: Update translations from Crowdin (#10686)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2025-02-12 12:34:34 +05:30
Sivin VargheseandGitHub cd80bd07ca fix: TypeError - Cannot read properties of null (reading 'name') (#10887) 2025-02-12 12:31:54 +05:30
Shivam MishraandGitHub b3f616da76 feat: upgrade utils (#10884) 2025-02-12 11:52:01 +05:30
Shivam MishraandGitHub c838779932 feat: Use feature flags across the routes (#10797) 2025-02-11 16:36:20 -08:00
55d41b112b feat: Show shared contact's name in Telegram channel (#10856)
# Pull Request Template

## Description

This PR adds the ability to see the shared contact name in Telegram
channels.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

**Loom video**

https://www.loom.com/share/cd318056ad4d44d4a1fc4b5d4ad38d60?sid=26d833ae-ded9-4cf0-9af7-81eecfa37f19


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2025-02-11 19:39:54 +05:30
Shivam MishraandGitHub a780de4b64 refactor: show deprecation warnings in dev only (#10868)
Fixes: https://github.com/chatwoot/chatwoot/issues/10734
2025-02-11 19:15:46 +05:30
Sivin VargheseandGitHub cf025e0fa4 chore: Remove the background SVG from the help center (#10857)
# Pull Request Template

## Description

This PR will remove the hexagon background image from public portal.

Fixes
https://linear.app/chatwoot/issue/CW-4013/remove-the-background-from-the-help-center

## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Screenshots**
**Before**
<img width="1507" alt="image"
src="https://github.com/user-attachments/assets/3539631d-013b-4770-8bfc-643daf695e0d"
/>

<img width="1507" alt="image"
src="https://github.com/user-attachments/assets/8f511a12-7f77-4301-a0db-bb8ef6f0e37f"
/>



**After**
<img width="1507" alt="image"
src="https://github.com/user-attachments/assets/4eb1a072-991c-4632-8c10-c7551def3a24"
/>

<img width="1507" alt="image"
src="https://github.com/user-attachments/assets/555e9f69-317a-4bc9-97aa-0990908729fa"
/>


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-11 18:58:36 +05:30
Shivam MishraandGitHub 84822a013a fix: inconsistent reply box cc update (#10799)
This PR target two issues

### CC & BCC not updated correctly

When moving from one conversation to another, the store may not have the
list of all the messages. A fetch is subsequently made to get the
messages. However, this update does not trigger the `currentChat`
watcher. This PR fixes it by adding a new watcher on
`currentChat.messages`.

We also update the `setCCAndToEmailsFromLastChat` method to reset the
`cc`, `bcc` and `to` fields if the last email is not found. This ensures
that the data is not carried forward from a previous email

Fixes: https://github.com/chatwoot/chatwoot/issues/10477

### To address are not added correctly to the `CC`

If the `to` address of a previous email has multiple recipient, there
was no case to add them to the CC.

Fixes: https://github.com/chatwoot/chatwoot/issues/8925

---

Depends on: https://github.com/chatwoot/utils/pull/41
2025-02-11 17:45:59 +05:30
Shivam MishraandGitHub a428dfc3f4 feat: handle mine event for incoming messages (#10867)
Handle `mine` condition, missed in
https://github.com/chatwoot/chatwoot/pull/10529
2025-02-11 17:45:31 +05:30
PranavandGitHub 4b12a8a51e chore: Add more conversation events for reload (#10877)
Followup PR for https://github.com/chatwoot/chatwoot/pull/10876. This PR
just adds all the events related to conversation update to be reloaded
before sending it to the UI.
2025-02-11 00:33:45 -08:00
PranavandGitHub 3c78d25306 chore: Reload conversation data in ActionCableBroadcastJob before sending (#10876)
During high-traffic periods, events may appear out of order, causing the
conversation job to queue outdated data, which can lead to issues in the
UI. This update ensures that only the latest available data is sent to the UI.

The conversation object is refreshed before sending it to the UI.
2025-02-10 23:16:15 -08:00
PranavandGitHub 8faccba052 chore: Update the precision of the updated_at timestamp in conversation model (#10875)
Use to_f instead of to_i to preserve the millisecond precision in the UI.
2025-02-10 20:22:11 -08:00
PranavandGitHub 02000de905 chore: Add updated_at attribute to the conversation event (#10873)
This PR adds updated_at attribute to the conversation event.
2025-02-10 19:33:26 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d7c0507e33 chore(deps): Bump net-imap from 0.4.17 to 0.4.19 (#10871)
Bumps [net-imap](https://github.com/ruby/net-imap) from 0.4.17 to
0.4.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ruby/net-imap/releases">net-imap's
releases</a>.</em></p>
<blockquote>
<h2>v0.4.19</h2>
<h2>What's Changed</h2>
<h3>🔒 Security Fix</h3>
<p>Fixes CVE-2025-25186 (GHSA-7fc5-f82f-cx69): A malicious server can
exhaust client memory by sending <code>APPENDUID</code> or
<code>COPYUID</code> responses with very large <code>uid-set</code>
ranges. <code>Net::IMAP::UIDPlusData</code> expands these ranges into
arrays of integers.</p>
<h4>Fix with minor API changes</h4>
<p>Set <code>config.parser_use_deprecated_uidplus_data</code> to
<code>false</code> to replace <code>UIDPlusData</code> with
<code>AppendUIDData</code> and <code>CopyUIDData</code>. These classes
store their UIDs as <code>Net::IMAP::SequenceSet</code> objects
(<em>not</em> expanded into arrays of integers). Code that does not
handle <code>APPENDUID</code> or <code>COPYUID</code> responses should
not see any difference. Code that does handle these responses
<em>may</em> need to be updated.</p>
<p>For v0.3.8, this option is not available
For v0.4.19, the default value is <code>true</code>.
For v0.5.6, the default value is <code>:up_to_max_size</code>.
For v0.6.0, the only allowed value will be <code>false</code>
<em>(<code>UIDPlusData</code> will be removed from v0.6)</em>.</p>
<h4>Mitigate with backward compatible API</h4>
<p>Adjust <code>config.parser_max_deprecated_uidplus_data_size</code> to
limit the maximum <code>UIDPlusData</code> UID set size.
When <code>config.parser_use_deprecated_uidplus_data == true</code>,
larger sets will crash.
When <code>config.parser_use_deprecated_uidplus_data ==
:up_to_max_size</code>, larger sets will use <code>AppendUIDData</code>
or <code>CopyUIDData</code>.</p>
<p>For v0.3,8, this limit is <em>hard-coded</em> to 10,000.
For v0.4.19, this limit defaults to 1000.
For v0.5.6, this limit defaults to 100.
For v0.6.0, the only allowed value will be <code>0</code>
<em>(<code>UIDPlusData</code> will be removed from v0.6)</em>.</p>
<h4>Please Note: unhandled responses</h4>
<p>If the client does not add response handlers to prune unhandled
responses, a malicious server can still eventually exhaust all client
memory, by repeatedly sending malicious responses. However,
<code>net-imap</code> has always retained unhandled responses, and it
has always been necessary for long-lived connections to prune these
responses. This is not significantly different from connecting to a
trusted server with a long-lived connection. To limit the maximum number
of retained responses, a simple handler might look something like the
following:</p>
<pre lang="ruby"><code>limit = 1000
imap.add_response_handler do |resp|
  next unless resp.respond_to?(:name) &amp;&amp; resp.respond_to?(:data)
  name = resp.name
code = resp.data.code&amp;.name if
resp.data.in?(Net::IMAP::ResponseText)
  imap.responses(name) { _1.slice!(0...-limit) }
  imap.responses(code) { _1.slice!(0...-limit) }
end
</code></pre>
<h3>Added</h3>
<ul>
<li>🔧 ResponseParser config is mutable and non-global (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/381">#381</a>) by
<a href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/382">ruby/net-imap#382</a></li>
<li> SequenceSet ordered entries methods (backports to v0.4-stable) by
<a href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/402">ruby/net-imap#402</a>
Backports the following:
<ul>
<li> Add SequenceSet methods for querying about duplicates by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/384">ruby/net-imap#384</a></li>
<li> Add <code>SequenceSet#each_ordered_number</code> by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/386">ruby/net-imap#386</a></li>
<li> Add <code>SequenceSet#find_ordered_index</code> by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/396">ruby/net-imap#396</a></li>
<li> Add <code>SequenceSet#ordered_at</code> by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/397">ruby/net-imap#397</a></li>
</ul>
</li>
<li> Backport UIDPlusData, AppendUIDData, CopyUIDData to v0.4 by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/404">ruby/net-imap#404</a>
Backports the following:
<ul>
<li> Add AppendUIDData and CopyUIDData classes by <a
href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/400">ruby/net-imap#400</a></li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ruby/net-imap/commit/4c4ed09997ccd108a50fd7b30edb1b32806a162f"><code>4c4ed09</code></a>
🔖 Bump version to 0.4.19</li>
<li><a
href="https://github.com/ruby/net-imap/commit/c8c5a643739d2669f0c9a6bb9770d0c045fd74a3"><code>c8c5a64</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/ruby/net-imap/commit/abff00fd700bd71ece3b3f841d4428028f16b8bb"><code>abff00f</code></a>
🔧 Add <code>:up_to_max_size</code> config for UIDPlusData</li>
<li><a
href="https://github.com/ruby/net-imap/commit/34a1f27a457be5236848543f3221b33c2494a34c"><code>34a1f27</code></a>
🔧 Add config option for max UIDPlusData size</li>
<li><a
href="https://github.com/ruby/net-imap/commit/6613d57e8eb885d65903819a2f83919a164a3680"><code>6613d57</code></a>
🔒 Limit exponential memory usage to parse uid-set</li>
<li><a
href="https://github.com/ruby/net-imap/commit/e4d57b1e000794882d5bca0f4cf7aa0658a83da7"><code>e4d57b1</code></a>
🔀 Merge pull request <a
href="https://redirect.github.com/ruby/net-imap/issues/404">#404</a>
from ruby/backport-0.4-uidplus-deprecation</li>
<li><a
href="https://github.com/ruby/net-imap/commit/d32320a74980a8754c5da3389693af8f597ab39c"><code>d32320a</code></a>
🐛 Fix missing <code>Data.define</code> for new classes</li>
<li><a
href="https://github.com/ruby/net-imap/commit/3c592fc98c26f11043a2b8e112bc91f31d7efad0"><code>3c592fc</code></a>
🔧🗑️ Deprecate UIDPlusData, with config to upgrade</li>
<li><a
href="https://github.com/ruby/net-imap/commit/7e58ef35fae7b52eb24b39c87f5b7fc69fa3e757"><code>7e58ef3</code></a>
 Add CopyUIDData (to replace UIDPlusData)</li>
<li><a
href="https://github.com/ruby/net-imap/commit/4c601c3a8468104eba5f9ee474f753064bf62115"><code>4c601c3</code></a>
 Add AppendUIDData (to replace UIDPlusData)</li>
<li>Additional commits viewable in <a
href="https://github.com/ruby/net-imap/compare/v0.4.17...v0.4.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=net-imap&package-manager=bundler&previous-version=0.4.17&new-version=0.4.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 17:35:12 -08:00
Sivin VargheseandGitHub e97e68b1ba fix: Message signature is not appending (#10855)
# Pull Request Template

## Description

**Issue:** The message signature wasn't being appended to new email
conversations when a target inbox was selected.

**Solution:** To address this, a reusable `handleSignatureSetup`
function was created to manage the signature logic. The same logic was
applied in both cases, when the inbox selection changed (using `watch`)
and during the initial load (using `mounted`).

Fixes
https://linear.app/chatwoot/issue/CW-4005/allow-to-activate-the-message-signature-for-new-email-conversations-by
https://github.com/chatwoot/chatwoot/issues/10836

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Steps to reproduce**:
https://github.com/chatwoot/chatwoot/issues/10836#issuecomment-2637354304

### Loom video

**Before**

https://www.loom.com/share/ccf597cfa8d94d0eaff1222102901d2c?sid=abfea42b-425e-446e-8e92-99359b786607

**After**

https://www.loom.com/share/d9deddfcf8de48ab87e31911dfb774d8?sid=c1aac19b-b243-428e-9a9f-2ad9f4efe49c


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-07 16:28:27 +05:30
be1999e7f8 fix: re-rendering of components when shifting from the unread list to the read list (#10835)
Fixes https://github.com/chatwoot/chatwoot/issues/10812

Demo


https://github.com/user-attachments/assets/a0f7eb64-8f6e-4992-a163-c972e85fb205

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-02-07 08:54:25 +05:30
Vishnu NarayananandGitHub c3601e16cf chore: bump up cwctl version to 3.2.0 (#10850) 2025-02-06 17:44:18 +05:30
Sivin VargheseandGitHub dc728faafb feat: Adds support for telegram contact sharing (#10841)
# Pull Request Template

## Description

This PR adds support for displaying shared contacts in a Telegram
channel.

**NB:** Tested with both old and new bubbles. 
Multiple numbers for a single contact are not supported at this time,
but multiple contacts are supported.
In the future, we can add support for displaying contact names as well.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

**Loom video**

https://www.loom.com/share/95efadace3194887bc0663c53e7c08bc?sid=a5c27176-3dd8-456c-80b9-c63dbb89dca1


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-02-06 14:23:08 +05:30
2a365bf19e feat: show email subject in conversation search results (#10843)
# Pull Request Template

## Description

This addresses #10842. It exposes `additional_attributes` in the
conversations search endpoint, uses it in
`SearchResultConversationsList` to pass
`conversation.additional_attributes?.mail_subject` down to
`SearchResultConversationItem`, which in turn displays it.

Fixes #10842

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

I have tested this locally by searching for conversations. See this
screenshot where I searched for "noreply":
![Screenshot from 2025-02-05
11-04-54](https://github.com/user-attachments/assets/689e3e99-c20b-48a7-9c3e-35d45ffeafc1)

I would love to add automated tests but I’m not sure how to do that.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2025-02-06 10:38:57 +05:30
d5ecbba71f fix: onboarding/index.html.erb unclosed HTML tags (#10838)
# Pull Request Template

## Description

This was not really an issue because HTML is permissive and auto-closes
these when the parent is closed, but it’s cleaner to do it.
It was also showing errors if you open the project in an IDE.

## Type of change

- Chore

## How Has This Been Tested?

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2025-02-06 09:43:38 +05:30
Sivin VargheseandGitHub abcff9883f feat: Order previous conversations by last activity (#10825) 2025-02-05 19:50:25 -08:00
Shivam MishraandGitHub d463a5cc30 fix: incorrect sender name on email meta (#10837)
Fixes https://github.com/chatwoot/chatwoot/issues/10807
2025-02-06 08:52:02 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>PranavPranav
c750413094 chore(deps): Bump vitest from 2.0.1 to 3.0.5 (#10839)
Bumps
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
from 2.0.1 to 3.0.5

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
2025-02-05 12:48:27 -08:00
f2a7e1da6b fix: Corepack pnpm issue (#10840)
So, a while back Circle CI builds and Heroku builds started to fail. 

From all the threads I read, it seems like the [npm registry rotated
it's signing
keys](https://github.com/pnpm/pnpm/issues/9014#issuecomment-2616589753)
New pnpm versions were signed with the new key. Corepack, however,
bundles a static set of trusted keys (from Node’s release), so it
continued verifying signatures only against the old key. When it
encountered packages signed with the new key, Corepack’s integrity check
failed with “Cannot find matching keyid” errors.This mismatch caused
Corepack’s integrity check to fail with “Cannot find matching keyid”
errors.

Workarounds include the following

1. Updating Corepack (to 0.31.0), they [upgraded their
package](https://github.com/nodejs/corepack/releases/tag/v0.31.0) to
include the new integrity check keys. But we seldom control what's going
on with the CI, also, updating this across our scripts is going to be a
painful task. Besides Heroku has [made some
fixes](https://github.com/heroku/buildpacks-nodejs/pull/1010) around
this
2. Disabling integrity checks 🔥 #YOLO
3. Pinning `pnpm` to older versions, or pinning it to a newer version
with the checksum in place.

Doing the third one here, running `corepack use pnpm@9.15.5` fixes this,
[ref](https://github.com/pnpm/pnpm/issues/9014#issuecomment-2623761494)

We can get rid of this over time as CDN caches used by build systems are
refreshed. But the change in this PR is not disruptive in anyway, only
rigidly secure.

Fixes: https://github.com/chatwoot/chatwoot/issues/10832

---

Here are the threads to follow 

- https://github.com/pnpm/pnpm/issues/9014
- https://github.com/pnpm/pnpm/issues/9029
- https://github.com/nodejs/corepack/issues/612
- https://github.com/nodejs/corepack/issues/616
- https://github.com/heroku/buildpacks-nodejs/pull/1010

---------

Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
2025-02-05 10:12:29 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
41c7683e04 chore(deps-dev): bump vite from 5.4.8 to 5.4.12 (#10744)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite)
from 5.4.8 to 5.4.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v5.4.12</h2>
<p>This version contains a breaking change due to security fixes. See <a
href="https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6">https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6</a>
for more details.</p>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v5.4.11</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/ecd2375460edb4ae258fed4abe6c6f6ed7323b23/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v5.4.10</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v5.4.10/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v5.4.9</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v5.4.9/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->5.4.12 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix!: check host header to prevent DNS rebinding attacks and
introduce <code>server.allowedHosts</code> (<a
href="https://github.com/vitejs/vite/commit/9da4abc8dde7f032ca1f23f425c2060b9b9ebd34">9da4abc</a>)</li>
<li>fix!: default <code>server.cors: false</code> to disallow fetching
from untrusted origins (<a
href="https://github.com/vitejs/vite/commit/dfea38f1ff9f6fc0f0ca57927c527b0b9ffd2210">dfea38f</a>)</li>
<li>fix: verify token for HMR WebSocket connection (<a
href="https://github.com/vitejs/vite/commit/b71a5c89a1b4b913813ae665e6e04dd9d18c189c">b71a5c8</a>)</li>
<li>chore: add deps update changelog (<a
href="https://github.com/vitejs/vite/commit/ecd2375460edb4ae258fed4abe6c6f6ed7323b23">ecd2375</a>)</li>
</ul>
<h2><!-- raw HTML omitted -->5.4.11 (2024-11-11)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix(deps): update dependencies of postcss-modules (<a
href="https://github.com/vitejs/vite/commit/ceb15db613d107e29f7cc1d441364f7b5c831ed3">ceb15db</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18617">#18617</a></li>
</ul>
<h2><!-- raw HTML omitted -->5.4.10 (2024-10-23)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18367">#18367</a>,augment
hash for CSS files to prevent chromium erroring by loading previous fil
(<a
href="https://github.com/vitejs/vite/commit/7d1a3bcc436e1697b314bdc9d24c948664a1afb7">7d1a3bc</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18367">#18367</a>
<a
href="https://redirect.github.com/vitejs/vite/issues/18412">#18412</a></li>
</ul>
<h2><!-- raw HTML omitted -->5.4.9 (2024-10-14)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: bump launch-editor-middleware to v2.9.1 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18348">#18348</a>)
(<a
href="https://github.com/vitejs/vite/commit/508d9ab83412c36e33f4c4ca57b891171429cdd3">508d9ab</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18348">#18348</a></li>
<li>fix(css): fix lightningcss dep url resolution with custom root (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18125">#18125</a>)
(<a
href="https://github.com/vitejs/vite/commit/eae00b561e04f1fe1679d3acf4f88b3c42019e4d">eae00b5</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18125">#18125</a></li>
<li>fix(data-uri): only match ids starting with <code>data:</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18241">#18241</a>)
(<a
href="https://github.com/vitejs/vite/commit/96084d6e752c03332d101a50bce161a8e3f311cc">96084d6</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18241">#18241</a></li>
<li>fix(deps): bump tsconfck (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18322">#18322</a>)
(<a
href="https://github.com/vitejs/vite/commit/dc5434ce8781d206bcc4b55e90201691125e662c">dc5434c</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18322">#18322</a></li>
<li>fix(hmr): don't try to rewrite imports for direct CSS soft
invalidation (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18252">#18252</a>)
(<a
href="https://github.com/vitejs/vite/commit/851b258c346fdddd4467a12f41189b7855df8c43">851b258</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18252">#18252</a></li>
<li>fix(ssr): (backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18150">#18150</a>)
fix source map remapping with multiple sources (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18204">#18204</a>)
(<a
href="https://github.com/vitejs/vite/commit/262a8796d4be2c4b9c812f203ea9177f42360b13">262a879</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18204">#18204</a></li>
<li>chore: update all url references of vitejs.dev to vite.dev (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18276">#18276</a>)
(<a
href="https://github.com/vitejs/vite/commit/c23558a7af341d13f0c9da691047713965bc7e7d">c23558a</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18276">#18276</a></li>
<li>chore: update license copyright (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18278">#18278</a>)
(<a
href="https://github.com/vitejs/vite/commit/1864eb17b21ef21564bd66c6f6a30c2c495e2d4e">1864eb1</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18278">#18278</a></li>
<li>docs: update homepage (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18274">#18274</a>)
(<a
href="https://github.com/vitejs/vite/commit/ae4416349e1a373023d0e9e05955d96ae5fa9ab2">ae44163</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18274">#18274</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/f428aa9af8534b214abb09fe4456653eb09913e7"><code>f428aa9</code></a>
release: v5.4.12</li>
<li><a
href="https://github.com/vitejs/vite/commit/9da4abc8dde7f032ca1f23f425c2060b9b9ebd34"><code>9da4abc</code></a>
fix!: check host header to prevent DNS rebinding attacks and introduce
`serve...</li>
<li><a
href="https://github.com/vitejs/vite/commit/b71a5c89a1b4b913813ae665e6e04dd9d18c189c"><code>b71a5c8</code></a>
fix: verify token for HMR WebSocket connection</li>
<li><a
href="https://github.com/vitejs/vite/commit/dfea38f1ff9f6fc0f0ca57927c527b0b9ffd2210"><code>dfea38f</code></a>
fix!: default <code>server.cors: false</code> to disallow fetching from
untrusted origins</li>
<li><a
href="https://github.com/vitejs/vite/commit/ecd2375460edb4ae258fed4abe6c6f6ed7323b23"><code>ecd2375</code></a>
chore: add deps update changelog</li>
<li><a
href="https://github.com/vitejs/vite/commit/c54c860f9d90e4074e5321648f9c5ee9fbda7038"><code>c54c860</code></a>
release: v5.4.11</li>
<li><a
href="https://github.com/vitejs/vite/commit/5f52bc8b9e4090cdcaf3f704278db30dafc825cc"><code>5f52bc8</code></a>
release: v5.4.10</li>
<li><a
href="https://github.com/vitejs/vite/commit/7d1a3bcc436e1697b314bdc9d24c948664a1afb7"><code>7d1a3bc</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18367">#18367</a>,augment
hash for CSS files to prevent chromium erroring ...</li>
<li><a
href="https://github.com/vitejs/vite/commit/898d61f94b4316993963f593644821aae221d375"><code>898d61f</code></a>
release: v5.4.9</li>
<li><a
href="https://github.com/vitejs/vite/commit/508d9ab83412c36e33f4c4ca57b891171429cdd3"><code>508d9ab</code></a>
fix: bump launch-editor-middleware to v2.9.1 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18348">#18348</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v5.4.12/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=vite&package-manager=npm_and_yarn&previous-version=5.4.8&new-version=5.4.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 18:13:48 -08:00
PranavandGitHub 1b1ba3f8dd fix: Update the photo/video caption when an update event is received (#10804)
The update ensures proper handling of text updates in photo/video
messages by accounting for the caption attribute in addition to the text
attribute. This change enables consistent processing across both
messages.

Fixes https://github.com/chatwoot/chatwoot/issues/10760

Note: TIL, you can update the video/photo you’ve sent on Telegram, not
just the text. Currently, we’re not handling this. To support it, we
need to parse the payload and update the attachments accordingly. This
could be taken as a followup.
2025-02-03 12:44:10 -08:00
Sivin VargheseandGitHub bd94e5062d chore: Search improvements (#10801)
- Adds pagination support for search.
- Use composition API on all search related component.
- Minor UI improvements.
- Adds missing specs

Loom video
https://www.loom.com/share/5b01afa5c9204e7d97ff81b215621dde?sid=82ca6d22-ca8c-4d5e-8740-ba06ca4051ba
2025-02-03 19:34:50 +05:30
Sojan JoseandGitHub 3fb77fe806 chore: Resolve flaky spec for Contact country sorting (#10810)
We have been encountering errors in the community pipeline for the
contacts sort by country spec. Upon investigation, it was discovered
that the spec assumes the country code is used for sorting. However, the
sorting actually relies on the country attribute.

The payload from a previous spec run indicates that none of the contact
objects include the country attribute. This fix addresses the issue by
aligning the spec with the actual implementation logic.

Here’s an example payload from the previous spec run for reference:
<img width="1063" alt="Screenshot 2025-01-31 at 6 17 44 PM"
src="https://github.com/user-attachments/assets/df9b26e8-c5d0-432a-9d2a-16f76cb66967"
/>
2025-02-03 16:54:13 +05:30
PranavandGitHub c7d259d5fd chore: Update the behavior of Captain resolutions (#10794)
This PR ensures that only conversations from quick conversation channels
are resolved, avoiding resolutions on the email channel (we still need
to improve the UX here). It also updates the FAQ generation logic,
limiting it to conversations that had at least one human interaction.
2025-02-03 16:25:08 +05:30
Sivin VargheseandGitHub 5905b5301d fix: Prevent compose modal from closing when creating a link (#10809) 2025-02-03 15:08:20 +05:30
Sojan 35f4e63605 Merge branch 'release/4.0.1'
Publish Chatwoot CE docker images / build (push) Waiting to run
2025-01-17 01:13:27 +05:30
Sojan 5773089865 Merge branch 'release/4.0.0'
Publish Chatwoot CE docker images / build (push) Waiting to run
2025-01-16 17:11:45 +05:30
Sojan d01c7d3fa7 Merge branch 'release/3.16.0'
Publish Chatwoot CE docker images / build (push) Waiting to run
2024-12-17 23:03:24 +05:30
Sojan 959d2c0d8c Merge branch 'release/3.15.0'
Publish Chatwoot CE docker images / build (push) Waiting to run
2024-11-19 18:02:07 +08:00
Vishnu Narayanan 622f29a0b7 Merge branch 'hotfix/3.14.1'
Publish Chatwoot CE docker images / build (push) Waiting to run
2024-10-22 16:03:47 +05:30
876 changed files with 27242 additions and 10660 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
steps:
- checkout
- node/install:
node-version: '20.12'
node-version: '23.7'
- node/install-pnpm
- node/install-packages:
pkg-manager: pnpm
+12 -12
View File
@@ -5,30 +5,30 @@
version: '3'
services:
base:
base:
build:
context: ..
dockerfile: .devcontainer/Dockerfile.base
args:
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.3.3"
VARIANT: 'ubuntu-22.04'
NODE_VERSION: '23.7.0'
RUBY_VERSION: '3.3.3'
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: "1000"
USER_GID: "1000"
USER_UID: '1000'
USER_GID: '1000'
image: base:latest
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.3.3"
VARIANT: 'ubuntu-22.04'
NODE_VERSION: '23.7.0'
RUBY_VERSION: '3.3.3'
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: "1000"
USER_GID: "1000"
USER_UID: '1000'
USER_GID: '1000'
volumes:
- ..:/workspace:cached
+1 -3
View File
@@ -23,12 +23,10 @@ jobs:
bundler-cache: true
- uses: pnpm/action-setup@v4
with:
version: 9.3.0
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 23
cache: 'pnpm'
- name: Install pnpm dependencies
+140
View File
@@ -0,0 +1,140 @@
# #
# # This action will publish Chatwoot EE docker image.
# # This is set to run against merges to develop, master
# # and when tags are created.
# #
name: Publish Chatwoot EE docker images
on:
push:
branches:
- develop
- master
tags:
- v*
workflow_dispatch:
env:
DOCKER_REPO: chatwoot/chatwoot
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-22.04-arm
runs-on: ${{ matrix.runner }}
env:
GIT_REF: ${{ github.head_ref || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set Chatwoot edition
run: |
echo -en '\nENV CW_EDITION="ee"' >> docker/Dockerfile
- name: Set Docker Tags
run: |
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
if [ "${{ github.ref_name }}" = "master" ]; then
echo "DOCKER_TAG=${DOCKER_REPO}:latest" >> $GITHUB_ENV
else
echo "DOCKER_TAG=${DOCKER_REPO}:${SANITIZED_REF}" >> $GITHUB_ENV
fi
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
platforms: ${{ matrix.platform }}
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
env:
GIT_REF: ${{ github.head_ref || github.ref_name }}
run: |
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
if [ "${{ github.ref_name }}" = "master" ]; then
TAG="${DOCKER_REPO}:latest"
else
TAG="${DOCKER_REPO}:${SANITIZED_REF}"
fi
docker buildx imagetools create -t $TAG \
$(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *)
- name: Inspect image
env:
GIT_REF: ${{ github.head_ref || github.ref_name }}
run: |
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
if [ "${{ github.ref_name }}" = "master" ]; then
TAG="${DOCKER_REPO}:latest"
else
TAG="${DOCKER_REPO}:${SANITIZED_REF}"
fi
docker buildx imagetools inspect $TAG
+1 -2
View File
@@ -38,7 +38,6 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -48,7 +47,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 23
cache: 'pnpm'
- name: Install pnpm dependencies
+3 -7
View File
@@ -19,13 +19,11 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: pnpm/action-setup@v2
with:
version: 9.3.0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 23
cache: 'pnpm'
- name: pnpm
@@ -39,7 +37,7 @@ jobs:
- name: setup env
run: |
cp .env.example .env
- name: Run asset compile
run: bundle exec rake assets:precompile
env:
@@ -47,5 +45,3 @@ jobs:
- name: Size Check
run: pnpm run size
+40
View File
@@ -0,0 +1,40 @@
name: Test Docker Build
on:
pull_request:
branches:
- develop
- master
workflow_dispatch:
jobs:
test-build:
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-22.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
platforms: ${{ matrix.platform }}
push: false
load: false
cache-from: type=gha
cache-to: type=gha,mode=max
+8 -8
View File
@@ -1,11 +1,11 @@
# #!/bin/sh
# . "$(dirname "$0")/_/husky.sh"
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# # lint js and vue files
# npx --no-install lint-staged
# lint js and vue files
npx --no-install lint-staged
# # lint only staged ruby files
# git diff --name-only --cached | xargs ls -1 2>/dev/null | grep '\.rb$' | xargs bundle exec rubocop --force-exclusion -a
# lint only staged ruby files
git diff --name-only --cached | xargs ls -1 2>/dev/null | grep '\.rb$' | xargs bundle exec rubocop --force-exclusion -a
# # stage rubocop changes to files
# git diff --name-only --cached | xargs git add
# stage rubocop changes to files
git diff --name-only --cached | xargs git add
+2 -4
View File
@@ -94,7 +94,7 @@ gem 'twitty', '~> 0.1.5'
# facebook client
gem 'koala'
# slack client
gem 'slack-ruby-client', '~> 2.2.0'
gem 'slack-ruby-client', '~> 2.5.1'
# for dialogflow integrations
gem 'google-cloud-dialogflow-v2', '>= 0.24.0'
gem 'grpc'
@@ -138,9 +138,7 @@ gem 'procore-sift'
# parse email
gem 'email_reply_trimmer'
# TODO: we might have to fork this gem since 0.3.1 has hard depency on nokogir 1.10.
# and this gem hasn't been updated for a while.
gem 'html2text', git: 'https://github.com/chatwoot/html2text_ruby', branch: 'chatwoot'
gem 'html2text'
# to calculate working hours
gem 'working_hours'
+17 -20
View File
@@ -22,14 +22,6 @@ GIT
devise (>= 4.0.0, < 5.0.0)
railties (>= 5.0.0, < 8.0.0)
GIT
remote: https://github.com/chatwoot/html2text_ruby
revision: cdbdbbbf898d846d0136d69d688a003c6b26074b
branch: chatwoot
specs:
html2text (0.3.1)
nokogiri (>= 1.13.6)
GEM
remote: https://rubygems.org/
specs:
@@ -186,7 +178,7 @@ GEM
database_cleaner-core (2.0.1)
datadog-ci (0.8.3)
msgpack
date (3.3.4)
date (3.4.1)
ddtrace (1.23.2)
datadog-ci (~> 0.8.1)
debase-ruby_core_source (= 3.3.1)
@@ -280,7 +272,8 @@ GEM
googleauth (~> 1.0)
grpc (~> 1.36)
geocoder (1.8.1)
gli (2.21.1)
gli (2.22.2)
ostruct
globalid (1.2.1)
activesupport (>= 6.1)
gmail_xoauth (0.4.3)
@@ -361,6 +354,8 @@ GEM
hana (1.3.7)
hashdiff (1.1.0)
hashie (5.0.0)
html2text (0.4.0)
nokogiri (>= 1.0, < 2.0)
http (5.1.1)
addressable (~> 2.8)
http-cookie (~> 1.0)
@@ -487,7 +482,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.17)
net-imap (0.4.19)
date
net-protocol
net-pop (0.1.2)
@@ -503,14 +498,14 @@ GEM
newrelic_rpm (9.6.0)
base64
nio4r (2.7.3)
nokogiri (1.17.1)
nokogiri (1.18.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.17.1-arm64-darwin)
nokogiri (1.18.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.17.1-x86_64-darwin)
nokogiri (1.18.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.17.1-x86_64-linux)
nokogiri (1.18.3-x86_64-linux-gnu)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
@@ -543,6 +538,7 @@ GEM
openssl (3.2.0)
orm_adapter (0.5.0)
os (1.1.4)
ostruct (0.6.1)
parallel (1.23.0)
parser (3.2.2.1)
ast (~> 2.4.1)
@@ -565,7 +561,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.10)
rack (2.2.11)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-contrib (2.5.0)
@@ -751,12 +747,13 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-ruby-client (2.2.0)
slack-ruby-client (2.5.1)
faraday (>= 2.0)
faraday-mashify
faraday-multipart
gli
hashie
logger
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
@@ -782,7 +779,7 @@ GEM
time_diff (0.3.0)
activesupport
i18n
timeout (0.4.1)
timeout (0.4.3)
trailblazer-option (0.1.2)
twilio-ruby (5.77.0)
faraday (>= 0.9, < 3.0)
@@ -897,7 +894,7 @@ DEPENDENCIES
haikunator
hairtrigger
hashie
html2text!
html2text
image_processing
jbuilder
json_refs
@@ -957,7 +954,7 @@ DEPENDENCIES
sidekiq (>= 7.3.1)
sidekiq-cron (>= 1.12.0)
simplecov (= 0.17.1)
slack-ruby-client (~> 2.2.0)
slack-ruby-client (~> 2.5.1)
spring
spring-watcher-listen
squasher
+1 -1
View File
@@ -1 +1 @@
3.1.0
3.2.0
@@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::
def index
@conversations = Current.account.conversations.includes(
:assignee, :contact, :inbox, :taggings
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(id: :desc).limit(20)
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(last_activity_at: :desc).limit(20)
end
private
@@ -10,7 +10,7 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
def create
authorize @inbox, :create?
ActiveRecord::Base.transaction do
agents_to_be_added_ids.map { |user_id| @inbox.add_member(user_id) }
@inbox.add_members(agents_to_be_added_ids)
end
fetch_updated_agents
end
@@ -24,7 +24,7 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
def destroy
authorize @inbox, :destroy?
ActiveRecord::Base.transaction do
params[:user_ids].map { |user_id| @inbox.remove_member(user_id) }
@inbox.remove_members(params[:user_ids])
end
head :ok
end
@@ -41,8 +41,8 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
# the missing ones are the agents which are to be deleted from the inbox
# the new ones are the agents which are to be added to the inbox
ActiveRecord::Base.transaction do
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
@inbox.add_members(agents_to_be_added_ids)
@inbox.remove_members(agents_to_be_removed_ids)
end
end
@@ -9,14 +9,14 @@ class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseControll
def create
ActiveRecord::Base.transaction do
@team_members = members_to_be_added_ids.map { |user_id| @team.add_member(user_id) }
@team_members = @team.add_members(members_to_be_added_ids)
end
end
def update
ActiveRecord::Base.transaction do
members_to_be_added_ids.each { |user_id| @team.add_member(user_id) }
members_to_be_removed_ids.each { |user_id| @team.remove_member(user_id) }
@team.add_members(members_to_be_added_ids)
@team.remove_members(members_to_be_removed_ids)
end
@team_members = @team.members
render action: 'create'
@@ -24,7 +24,7 @@ class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseControll
def destroy
ActiveRecord::Base.transaction do
params[:user_ids].map { |user_id| @team.remove_member(user_id) }
@team.remove_members(params[:user_ids])
end
head :ok
end
+6 -2
View File
@@ -7,13 +7,17 @@ class DashboardController < ActionController::Base
around_action :switch_locale
before_action :ensure_installation_onboarding, only: [:index]
before_action :render_hc_if_custom_domain, only: [:index]
before_action :ensure_html_format
layout 'vueapp'
def index; end
private
def ensure_html_format
head :not_acceptable unless request.format.html?
end
def set_global_config
@global_config = GlobalConfig.get(
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
@@ -32,7 +36,7 @@ class DashboardController < ActionController::Base
'LOGOUT_REDIRECT_LINK',
'DISABLE_USER_PROFILE_UPDATE',
'DEPLOYMENT_ENV',
'CSML_EDITOR_HOST'
'CSML_EDITOR_HOST', 'INSTALLATION_PRICING_PLAN'
).merge(app_config)
end
@@ -94,7 +94,8 @@ module Api::V2::Accounts::HeatmapHelper
end
def since_timestamp(date)
(date - 6.days).to_i.to_s
number_of_days = params[:days_before].present? ? params[:days_before].to_i.days : 6.days
(date - number_of_days).to_i.to_s
end
def until_timestamp(date)
+1 -2
View File
@@ -5,8 +5,7 @@ module PortalHelper
end
def generate_portal_bg(portal_color, theme)
bg_image = theme == 'dark' ? 'hexagon-dark.svg' : 'hexagon-light.svg'
"url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}"
generate_portal_bg_color(portal_color, theme)
end
def generate_gradient_to_bottom(theme)
@@ -6,4 +6,47 @@ module SuperAdmin::AccountFeaturesHelper
def self.account_premium_features
account_features.filter { |feature| feature['premium'] }.pluck('name')
end
# Returns a hash mapping feature names to their display names
def self.feature_display_names
account_features.each_with_object({}) do |feature, hash|
hash[feature['name']] = feature['display_name']
end
end
def self.filter_internal_features(features)
return features if GlobalConfig.get_value('DEPLOYMENT_ENV') == 'cloud'
internal_features = account_features.select { |f| f['chatwoot_internal'] }.pluck('name')
features.except(*internal_features)
end
def self.filter_deprecated_features(features)
deprecated_features = account_features.select { |f| f['deprecated'] }.pluck('name')
features.except(*deprecated_features)
end
def self.sort_and_transform_features(features, display_names)
features.sort_by { |key, _| display_names[key] || key }
.to_h
.transform_keys { |key| [key, display_names[key]] }
end
def self.partition_features(features)
filtered = filter_internal_features(features)
filtered = filter_deprecated_features(filtered)
display_names = feature_display_names
regular, premium = filtered.partition { |key, _value| account_premium_features.exclude?(key) }
[
sort_and_transform_features(regular, display_names),
sort_and_transform_features(premium, display_names)
]
end
def self.filtered_features(features)
regular, premium = partition_features(features)
regular.merge(premium)
end
end
+2 -2
View File
@@ -61,9 +61,9 @@ class ReportsAPI extends ApiClient {
});
}
getConversationTrafficCSV() {
getConversationTrafficCSV({ daysBefore = 6 } = {}) {
return axios.get(`${this.url}/conversation_traffic`, {
params: { timezone_offset: getTimeOffset() },
params: { timezone_offset: getTimeOffset(), days_before: daysBefore },
});
}
+6 -3
View File
@@ -14,26 +14,29 @@ class SearchAPI extends ApiClient {
});
}
contacts({ q }) {
contacts({ q, page = 1 }) {
return axios.get(`${this.url}/contacts`, {
params: {
q,
page: page,
},
});
}
conversations({ q }) {
conversations({ q, page = 1 }) {
return axios.get(`${this.url}/conversations`, {
params: {
q,
page: page,
},
});
}
messages({ q }) {
messages({ q, page = 1 }) {
return axios.get(`${this.url}/messages`, {
params: {
q,
page: page,
},
});
}
@@ -6,3 +6,209 @@
body {
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
}
@layer base {
// FIXME: Use a common color file for all packs
// scss-lint:disable PropertySortOrder
:root {
--slate-1: 252 252 253;
--slate-2: 249 249 251;
--slate-3: 240 240 243;
--slate-4: 232 232 236;
--slate-5: 224 225 230;
--slate-6: 217 217 224;
--slate-7: 205 206 214;
--slate-8: 185 187 198;
--slate-9: 139 141 152;
--slate-10: 128 131 141;
--slate-11: 96 100 108;
--slate-12: 28 32 36;
--iris-1: 253 253 255;
--iris-2: 248 248 255;
--iris-3: 240 241 254;
--iris-4: 230 231 255;
--iris-5: 218 220 255;
--iris-6: 203 205 255;
--iris-7: 184 186 248;
--iris-8: 155 158 240;
--iris-9: 91 91 214;
--iris-10: 81 81 205;
--iris-11: 87 83 198;
--iris-12: 39 41 98;
--ruby-1: 255 252 253;
--ruby-2: 255 247 248;
--ruby-3: 254 234 237;
--ruby-4: 255 220 225;
--ruby-5: 255 206 214;
--ruby-6: 248 191 200;
--ruby-7: 239 172 184;
--ruby-8: 229 146 163;
--ruby-9: 229 70 102;
--ruby-10: 220 59 93;
--ruby-11: 202 36 77;
--ruby-12: 100 23 43;
--amber-1: 254 253 251;
--amber-2: 254 251 233;
--amber-3: 255 247 194;
--amber-4: 255 238 156;
--amber-5: 251 229 119;
--amber-6: 243 214 115;
--amber-7: 233 193 98;
--amber-8: 226 163 54;
--amber-9: 255 197 61;
--amber-10: 255 186 24;
--amber-11: 171 100 0;
--amber-12: 79 52 34;
--teal-1: 250 254 253;
--teal-2: 243 251 249;
--teal-3: 224 248 243;
--teal-4: 204 243 234;
--teal-5: 184 234 224;
--teal-6: 161 222 210;
--teal-7: 131 205 193;
--teal-8: 83 185 171;
--teal-9: 18 165 148;
--teal-10: 13 155 138;
--teal-11: 0 133 115;
--teal-12: 13 61 56;
--gray-1: 252 252 252;
--gray-2: 249 249 249;
--gray-3: 240 240 240;
--gray-4: 232 232 232;
--gray-5: 224 224 224;
--gray-6: 217 217 217;
--gray-7: 206 206 206;
--gray-8: 187 187 187;
--gray-9: 141 141 141;
--gray-10: 131 131 131;
--gray-11: 100 100 100;
--gray-12: 32 32 32;
--background-color: 253 253 253;
--text-blue: 8 109 224;
--border-container: 236 236 236;
--border-strong: 235 235 235;
--border-weak: 234 234 234;
--solid-1: 255 255 255;
--solid-2: 255 255 255;
--solid-3: 255 255 255;
--solid-active: 255 255 255;
--solid-amber: 252 232 193;
--solid-blue: 218 236 255;
--solid-iris: 230 231 255;
--alpha-1: 67, 67, 67, 0.06;
--alpha-2: 201, 202, 207, 0.15;
--alpha-3: 255, 255, 255, 0.96;
--black-alpha-1: 0, 0, 0, 0.12;
--black-alpha-2: 0, 0, 0, 0.04;
--border-blue: 39, 129, 246, 0.5;
--white-alpha: 255, 255, 255, 0.8;
}
.dark {
--slate-1: 17 17 19;
--slate-2: 24 25 27;
--slate-3: 33 34 37;
--slate-4: 39 42 45;
--slate-5: 46 49 53;
--slate-6: 54 58 63;
--slate-7: 67 72 78;
--slate-8: 90 97 105;
--slate-9: 105 110 119;
--slate-10: 119 123 132;
--slate-11: 176 180 186;
--slate-12: 237 238 240;
--iris-1: 19 19 30;
--iris-2: 23 22 37;
--iris-3: 32 34 72;
--iris-4: 38 42 101;
--iris-5: 48 51 116;
--iris-6: 61 62 130;
--iris-7: 74 74 149;
--iris-8: 89 88 177;
--iris-9: 91 91 214;
--iris-10: 84 114 228;
--iris-11: 158 177 255;
--iris-12: 224 223 254;
--ruby-1: 25 17 19;
--ruby-2: 30 21 23;
--ruby-3: 58 20 30;
--ruby-4: 78 19 37;
--ruby-5: 94 26 46;
--ruby-6: 111 37 57;
--ruby-7: 136 52 71;
--ruby-8: 179 68 90;
--ruby-9: 229 70 102;
--ruby-10: 236 90 114;
--ruby-11: 255 148 157;
--ruby-12: 254 210 225;
--amber-1: 22 18 12;
--amber-2: 29 24 15;
--amber-3: 48 32 8;
--amber-4: 63 39 0;
--amber-5: 77 48 0;
--amber-6: 92 61 5;
--amber-7: 113 79 25;
--amber-8: 143 100 36;
--amber-9: 255 197 61;
--amber-10: 255 214 10;
--amber-11: 255 202 22;
--amber-12: 255 231 179;
--teal-1: 13 21 20;
--teal-2: 17 28 27;
--teal-3: 13 45 42;
--teal-4: 2 59 55;
--teal-5: 8 72 67;
--teal-6: 20 87 80;
--teal-7: 28 105 97;
--teal-8: 32 126 115;
--teal-9: 18 165 148;
--teal-10: 14 179 158;
--teal-11: 11 216 182;
--teal-12: 173 240 221;
--gray-1: 17 17 17;
--gray-2: 25 25 25;
--gray-3: 34 34 34;
--gray-4: 42 42 42;
--gray-5: 49 49 49;
--gray-6: 58 58 58;
--gray-7: 72 72 72;
--gray-8: 96 96 96;
--gray-9: 110 110 110;
--gray-10: 123 123 123;
--gray-11: 180 180 180;
--gray-12: 238 238 238;
--background-color: 18 18 19;
--border-strong: 52 52 52;
--border-weak: 38 38 42;
--solid-1: 23 23 26;
--solid-2: 29 30 36;
--solid-3: 44 45 54;
--solid-active: 53 57 66;
--solid-amber: 42 37 30;
--solid-blue: 16 49 91;
--solid-iris: 38 42 101;
--text-blue: 126 182 255;
--alpha-1: 36, 36, 36, 0.8;
--alpha-2: 139, 147, 182, 0.15;
--alpha-3: 36, 38, 45, 0.9;
--black-alpha-1: 0, 0, 0, 0.3;
--black-alpha-2: 0, 0, 0, 0.2;
--border-blue: 39, 129, 246, 0.5;
--border-container: 236, 236, 236, 0;
--white-alpha: 255, 255, 255, 0.1;
}
}
@@ -21,11 +21,11 @@ const addCampaign = async campaignDetails => {
type: CAMPAIGN_TYPES.ONGOING,
});
useAlert(t('CAMPAIGN.SMS.CREATE.FORM.API.SUCCESS_MESSAGE'));
useAlert(t('CAMPAIGN.LIVE_CHAT.CREATE.FORM.API.SUCCESS_MESSAGE'));
} catch (error) {
const errorMessage =
error?.response?.message ||
t('CAMPAIGN.SMS.CREATE.FORM.API.ERROR_MESSAGE');
t('CAMPAIGN.LIVE_CHAT.CREATE.FORM.API.ERROR_MESSAGE');
useAlert(errorMessage);
}
};
@@ -8,17 +8,17 @@ import Breadcrumb from 'dashboard/components-next/breadcrumb/Breadcrumb.vue';
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
const props = defineProps({
buttonLabel: {
type: String,
default: '',
},
selectedContact: {
type: Object,
default: () => ({}),
},
isUpdating: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['goToContactsList']);
const emit = defineEmits(['goToContactsList', 'toggleBlock']);
const { t } = useI18n();
const slots = useSlots();
@@ -45,9 +45,17 @@ const breadcrumbItems = computed(() => {
return items;
});
const isContactBlocked = computed(() => {
return props.selectedContact?.blocked;
});
const handleBreadcrumbClick = () => {
emit('goToContactsList');
};
const toggleBlock = () => {
emit('toggleBlock', isContactBlocked.value);
};
</script>
<template>
@@ -64,11 +72,29 @@ const handleBreadcrumbClick = () => {
:items="breadcrumbItems"
@click="handleBreadcrumbClick"
/>
<ComposeConversation :contact-id="contactId">
<template #trigger="{ toggle }">
<Button :label="buttonLabel" size="sm" @click="toggle" />
</template>
</ComposeConversation>
<div class="flex items-center gap-2">
<Button
:label="
!isContactBlocked
? $t('CONTACTS_LAYOUT.HEADER.BLOCK_CONTACT')
: $t('CONTACTS_LAYOUT.HEADER.UNBLOCK_CONTACT')
"
size="sm"
slate
:is-loading="isUpdating"
:disabled="isUpdating"
@click="toggleBlock"
/>
<ComposeConversation :contact-id="contactId">
<template #trigger="{ toggle }">
<Button
:label="$t('CONTACTS_LAYOUT.HEADER.SEND_MESSAGE')"
size="sm"
@click="toggle"
/>
</template>
</ComposeConversation>
</div>
</div>
</div>
</header>
@@ -171,6 +171,10 @@ watch(
{ immediate: true, deep: true }
);
const handleClickOutside = () => {
showComposeNewConversation.value = false;
};
onMounted(() => resetContacts());
const keyboardEvents = {
@@ -188,7 +192,12 @@ useKeyboardEvents(keyboardEvents);
<template>
<div
v-on-click-outside="() => (showComposeNewConversation = false)"
v-on-click-outside="[
handleClickOutside,
// Fixed and edge case https://github.com/chatwoot/chatwoot/issues/10785
// This will prevent closing the compose conversation modal when the editor Create link popup is open.
{ ignore: ['div.ProseMirror-prompt'] },
]"
class="relative"
:class="{
'z-40': showComposeNewConversation,
@@ -77,7 +77,7 @@ const toggleMessageSignature = () => {
setSignature();
};
// Added this watch to dynamically set signature.
// Added this watch to dynamically set signature on target inbox change.
// Only targetInbox has value and is Advance Editor(used by isEmailOrWebWidgetInbox)
// Set the signature only if the inbox based flag is true
watch(
@@ -86,7 +86,8 @@ watch(
nextTick(() => {
if (newValue && props.isEmailOrWebWidgetInbox) setSignature();
});
}
},
{ immediate: true }
);
const onClickInsertEmoji = emoji => {
@@ -1,12 +1,12 @@
<script setup>
import { computed } from 'vue';
import { useAccount } from 'dashboard/composables/useAccount';
import { usePolicy } from 'dashboard/composables/usePolicy';
import Button from 'dashboard/components-next/button/Button.vue';
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Policy from 'dashboard/components/policy.vue';
const { featureFlag } = defineProps({
const props = defineProps({
currentPage: {
type: Number,
default: 1,
@@ -50,10 +50,10 @@ const { featureFlag } = defineProps({
});
const emit = defineEmits(['click', 'close', 'update:currentPage']);
const { isCloudFeatureEnabled } = useAccount();
const { shouldShowPaywall } = usePolicy();
const showPaywall = computed(() => {
return !isCloudFeatureEnabled(featureFlag);
return shouldShowPaywall(props.featureFlag);
});
const handleButtonClick = () => {
@@ -97,7 +97,7 @@ const handlePageChange = event => {
</header>
<main class="flex-1 px-6 overflow-y-auto xl:px-0">
<div class="w-full max-w-[960px] mx-auto py-4">
<slot name="controls" />
<slot v-if="!showPaywall" name="controls" />
<div
v-if="isFetching"
class="flex items-center justify-center py-10 text-n-slate-11"
@@ -1,6 +1,21 @@
<script setup>
import { useAttrs } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
const attrs = useAttrs();
const globalConfig = useMapGetter('globalConfig/get');
</script>
<template>
<img
v-if="globalConfig.logoThumbnail"
v-bind="attrs"
:src="globalConfig.logoThumbnail"
/>
<svg
v-else
v-once
v-bind="attrs"
width="16"
height="16"
viewBox="0 0 16 16"
@@ -414,6 +414,11 @@ const avatarInfo = computed(() => {
};
});
const avatarTooltip = computed(() => {
if (avatarInfo.value.name === '') return '';
return `${t('CONVERSATION.SENT_BY')} ${avatarInfo.value.name}`;
});
const setupHighlightTimer = () => {
if (Number(route.query.messageId) !== Number(props.id)) {
return;
@@ -472,6 +477,7 @@ provideMessageContext({
>
<div
v-if="!shouldGroupWithNext && shouldShowAvatar"
v-tooltip.right-end="avatarTooltip"
class="[grid-area:avatar] flex items-end"
>
<Avatar v-bind="avatarInfo" :size="24" />
@@ -15,18 +15,14 @@ import { useCamelCase } from 'dashboard/composables/useTransformKeys';
* @property {Array} messages - Array of all messages [These are not in camelcase]
*/
const props = defineProps({
readMessages: {
type: Array,
default: () => [],
},
unReadMessages: {
type: Array,
default: () => [],
},
currentUserId: {
type: Number,
required: true,
},
firstUnreadId: {
type: Number,
default: null,
},
isAnEmailChannel: {
type: Boolean,
default: false,
@@ -41,12 +37,8 @@ const props = defineProps({
},
});
const unread = computed(() => {
return useCamelCase(props.unReadMessages, { deep: true });
});
const read = computed(() => {
return useCamelCase(props.readMessages, { deep: true });
const allMessages = computed(() => {
return useCamelCase(props.messages, { deep: true });
});
/**
@@ -108,26 +100,18 @@ const getInReplyToMessage = parentMessage => {
<template>
<ul class="px-4 bg-n-background">
<slot name="beforeAll" />
<template v-for="(message, index) in read" :key="message.id">
<Message
v-bind="message"
:is-email-inbox="isAnEmailChannel"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, read)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
data-clarity-mask="True"
<template v-for="(message, index) in allMessages" :key="message.id">
<slot
v-if="firstUnreadId && message.id === firstUnreadId"
name="unreadBadge"
/>
</template>
<slot name="beforeUnread" />
<template v-for="(message, index) in unread" :key="message.id">
<Message
v-bind="message"
:is-email-inbox="isAnEmailChannel"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, unread)"
:group-with-next="shouldGroupWithNext(index, allMessages)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
:is-email-inbox="isAnEmailChannel"
data-clarity-mask="True"
/>
</template>
@@ -10,6 +10,7 @@ defineProps({
iconBgColor: { type: String, default: 'bg-n-alpha-3' },
senderTranslationKey: { type: String, required: true },
content: { type: String, required: true },
title: { type: String, default: '' }, // Title can be any name, description, etc
action: {
type: Object,
required: true,
@@ -23,14 +24,14 @@ const { sender } = useMessageContext();
const { t } = useI18n();
const senderName = computed(() => {
return sender?.value.name;
return sender?.value?.name || '';
});
</script>
<template>
<BaseBubble class="overflow-hidden p-3" data-bubble-name="attachment">
<div class="grid gap-4 min-w-64">
<div class="grid gap-3 z-20">
<div class="grid gap-3">
<div
class="size-8 rounded-lg grid place-content-center"
:class="iconBgColor"
@@ -48,6 +49,9 @@ const senderName = computed(() => {
}}
</div>
<slot>
<div v-if="title" class="truncate text-sm text-n-slate-12">
{{ title }}
</div>
<div v-if="content" class="truncate text-sm text-n-slate-11">
{{ content }}
</div>
@@ -11,7 +11,7 @@ import {
ExceptionWithMessage,
} from 'shared/helpers/CustomErrors';
const { content, attachments } = useMessageContext();
const { attachments } = useMessageContext();
const $store = useStore();
const { t } = useI18n();
@@ -24,6 +24,12 @@ const phoneNumber = computed(() => {
return attachment.value.fallbackTitle;
});
const contactName = computed(() => {
const { meta } = attachment.value ?? {};
const { firstName, lastName } = meta ?? {};
return `${firstName ?? ''} ${lastName ?? ''}`.trim();
});
const formattedPhoneNumber = computed(() => {
return phoneNumber.value.replace(/\s|-|[A-Za-z]/g, '');
});
@@ -32,13 +38,9 @@ const rawPhoneNumber = computed(() => {
return phoneNumber.value.replace(/\D/g, '');
});
const name = computed(() => {
return content.value;
});
function getContactObject() {
const contactItem = {
name: name.value,
name: contactName.value,
phone_number: `+${rawPhoneNumber.value}`,
};
return contactItem;
@@ -99,6 +101,7 @@ const action = computed(() => ({
icon="i-teenyicons-user-circle-solid"
icon-bg-color="bg-[#D6409F]"
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.CONTACT"
:title="contactName"
:content="phoneNumber"
:action="formattedPhoneNumber ? action : null"
/>
@@ -2,34 +2,30 @@
import { computed, ref } from 'vue';
import DyteAPI from 'dashboard/api/integrations/dyte';
import { buildDyteURL } from 'shared/helpers/IntegrationHelper';
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { useMessageContext } from '../provider.js';
import BaseAttachmentBubble from './BaseAttachment.vue';
const { contentAttributes } = useMessageContext();
const { content, sender, id } = useMessageContext();
const { t } = useI18n();
const meetingData = computed(() => {
return useCamelCase(contentAttributes.value.data);
});
const isLoading = ref(false);
const dyteAuthToken = ref('');
const meetingLink = computed(() => {
return buildDyteURL(meetingData.value.roomName, dyteAuthToken.value);
return buildDyteURL(dyteAuthToken.value);
});
const joinTheCall = async () => {
isLoading.value = true;
try {
const { data: { authResponse: { authToken } = {} } = {} } =
await DyteAPI.addParticipantToMeeting(meetingData.value.messageId);
dyteAuthToken.value = authToken;
const { data: { token } = {} } = await DyteAPI.addParticipantToMeeting(
id.value
);
dyteAuthToken.value = token;
} catch (err) {
useAlert(t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
} finally {
@@ -38,7 +34,7 @@ const joinTheCall = async () => {
};
const leaveTheRoom = () => {
this.dyteAuthToken = '';
dyteAuthToken.value = '';
};
const action = computed(() => ({
label: t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN'),
@@ -53,13 +49,18 @@ const action = computed(() => ({
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.MEETING"
:action="action"
>
<div v-if="!sender" class="text-sm truncate text-n-slate-12">
<!-- Added as a fallback, where the sender is not available (Deleted) -->
<!-- Will show the content, if senderName in BaseAttachment.vue is empty -->
{{ content }}
</div>
<div v-if="dyteAuthToken" class="video-call--container">
<iframe
:src="meetingLink"
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
/>
<button
class="bg-n-solid-3 px-4 py-2 rounded-lg text-sm"
class="px-4 py-2 text-sm rounded-lg bg-n-solid-3 mt-3"
@click="leaveTheRoom"
>
{{ $t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM') }}
@@ -26,7 +26,18 @@ const ccEmail = computed(() => {
});
const senderName = computed(() => {
return sender.value.name ?? '';
const fromEmailAddress = fromEmail.value[0] ?? '';
const senderEmail = sender.value.email ?? '';
if (!fromEmailAddress && !senderEmail) return null;
// if the sender of the conversation and the sender of this particular
// email are the same, only then we return the sender name
if (fromEmailAddress === senderEmail) {
return sender.value.name;
}
return null;
});
const bccEmail = computed(() => {
@@ -59,11 +70,19 @@ const showMeta = computed(() => {
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-11'"
>
<template v-if="showMeta">
<div v-if="fromEmail[0]">
<span :class="hasError ? 'text-n-ruby-11' : 'text-n-slate-12'">
{{ senderName }}
</span>
&lt;{{ fromEmail[0] }}&gt;
<div
v-if="fromEmail[0]"
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-12'"
>
<template v-if="senderName">
<span>
{{ senderName }}
</span>
&lt;{{ fromEmail[0] }}&gt;
</template>
<template v-else>
{{ fromEmail[0] }}
</template>
</div>
<div v-if="toEmail.length">
{{ $t('EMAIL_HEADER.TO') }}: {{ toEmail.join(', ') }}
@@ -1,6 +1,7 @@
<script setup>
import { computed, useTemplateRef, ref, onMounted } from 'vue';
import { Letter } from 'vue-letter';
import { allowedCssProperties } from 'lettersanitizer';
import Icon from 'next/icon/Icon.vue';
import { EmailQuoteExtractor } from './removeReply.js';
@@ -29,8 +30,15 @@ const isOutgoing = computed(() => {
});
const isIncoming = computed(() => !isOutgoing.value);
const textToShow = computed(() => {
const text =
contentAttributes?.value?.email?.textContent?.full ?? content.value;
return text?.replace(/\n/g, '<br>');
});
// Use TextContent as the default to fullHTML
const fullHTML = computed(() => {
return contentAttributes?.value?.email?.htmlContent?.full ?? content.value;
return contentAttributes?.value?.email?.htmlContent?.full ?? textToShow.value;
});
const unquotedHTML = computed(() => {
@@ -40,12 +48,6 @@ const unquotedHTML = computed(() => {
const hasQuotedMessage = computed(() => {
return EmailQuoteExtractor.hasQuotes(fullHTML.value);
});
const textToShow = computed(() => {
const text =
contentAttributes?.value?.email?.textContent?.full ?? content.value;
return text?.replace(/\n/g, '<br>');
});
</script>
<template>
@@ -92,6 +94,11 @@ const textToShow = computed(() => {
<Letter
v-if="showQuotedMessage"
class-name="prose prose-bubble !max-w-none"
:allowed-css-properties="[
...allowedCssProperties,
'transform',
'transform-origin',
]"
:html="fullHTML"
:text="textToShow"
/>
@@ -99,6 +106,11 @@ const textToShow = computed(() => {
v-else
class-name="prose prose-bubble !max-w-none"
:html="unquotedHTML"
:allowed-css-properties="[
...allowedCssProperties,
'transform',
'transform-origin',
]"
:text="textToShow"
/>
</template>
@@ -25,22 +25,29 @@ const isPlaying = ref(false);
const isMuted = ref(false);
const currentTime = ref(0);
const duration = ref(0);
const playbackSpeed = ref(1);
const onLoadedMetadata = () => {
duration.value = audioPlayer.value?.duration;
};
const playbackSpeedLabel = computed(() => {
return `${playbackSpeed.value}x`;
});
// There maybe a chance that the audioPlayer ref is not available
// When the onLoadMetadata is called, so we need to set the duration
// value when the component is mounted
onMounted(() => {
duration.value = audioPlayer.value?.duration;
audioPlayer.value.playbackRate = playbackSpeed.value;
});
const formatTime = time => {
if (!time || Number.isNaN(time)) return '00:00';
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
const toggleMute = () => {
@@ -49,7 +56,7 @@ const toggleMute = () => {
};
const onTimeUpdate = () => {
currentTime.value = audioPlayer.value.currentTime;
currentTime.value = audioPlayer.value?.currentTime;
};
const seek = event => {
@@ -71,6 +78,16 @@ const playOrPause = () => {
const onEnd = () => {
isPlaying.value = false;
currentTime.value = 0;
playbackSpeed.value = 1;
audioPlayer.value.playbackRate = 1;
};
const changePlaybackSpeed = () => {
const speeds = [1, 1.5, 2];
const currentIndex = speeds.indexOf(playbackSpeed.value);
const nextIndex = (currentIndex + 1) % speeds.length;
playbackSpeed.value = speeds[nextIndex];
audioPlayer.value.playbackRate = playbackSpeed.value;
};
const downloadAudio = async () => {
@@ -105,7 +122,7 @@ const downloadAudio = async () => {
<div class="tabular-nums text-xs">
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
</div>
<div class="flex items-center px-2">
<div class="flex-1 items-center flex px-2">
<input
type="range"
min="0"
@@ -115,6 +132,14 @@ const downloadAudio = async () => {
@input="seek"
/>
</div>
<button
class="border-0 w-10 h-6 grid place-content-center bg-n-alpha-2 hover:bg-alpha-3 rounded-2xl"
@click="changePlaybackSpeed"
>
<span class="text-xs text-n-slate-11 font-medium">
{{ playbackSpeedLabel }}
</span>
</button>
<button
class="p-0 border-0 size-8 grid place-content-center"
@click="toggleMute"
@@ -240,24 +240,20 @@ const menuItems = computed(() => {
name: 'Captain',
icon: 'i-woot-captain',
label: t('SIDEBAR.CAPTAIN'),
showOnlyOnCloud: true,
children: [
{
name: 'Assistants',
label: t('SIDEBAR.CAPTAIN_ASSISTANTS'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_assistants_index'),
},
{
name: 'Documents',
label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_documents_index'),
},
{
name: 'Responses',
label: t('SIDEBAR.CAPTAIN_RESPONSES'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_responses_index'),
},
],
@@ -509,7 +505,6 @@ const menuItems = computed(() => {
name: 'Settings Billing',
label: t('SIDEBAR.BILLING'),
icon: 'i-lucide-credit-card',
showOnlyOnCloud: true,
to: accountScopedRoute('billing_settings_index'),
},
],
@@ -525,7 +520,7 @@ const menuItems = computed(() => {
<section class="grid gap-2 mt-2 mb-4">
<div class="flex items-center min-w-0 gap-2 px-2">
<div class="grid flex-shrink-0 size-6 place-content-center">
<Logo />
<Logo class="size-4" />
</div>
<div class="flex-shrink-0 w-px h-3 bg-n-strong" />
<SidebarAccountSwitcher
@@ -24,7 +24,6 @@ const {
resolvePath,
resolvePermissions,
resolveFeatureFlag,
isOnChatwootCloud,
isAllowed,
} = useSidebarContext();
@@ -43,7 +42,6 @@ const hasChildren = computed(
const accessibleItems = computed(() => {
if (!hasChildren.value) return [];
return props.children.filter(child => {
if (child.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
// If a item has no link, it means it's just a subgroup header
// So we don't need to check for permissions here, because there's nothing to
// access here anyway
@@ -166,7 +164,7 @@ onMounted(async () => {
:active-child="activeChild"
/>
<SidebarGroupLeaf
v-else
v-else-if="isAllowed(child.to)"
v-show="isExpanded || activeChild?.name === child.name"
v-bind="child"
:active="activeChild?.name === child.name"
@@ -9,18 +9,10 @@ const props = defineProps({
to: { type: [String, Object], required: true },
icon: { type: [String, Object], default: null },
active: { type: Boolean, default: false },
showOnlyOnCloud: { type: Boolean, default: false },
component: { type: Function, default: null },
});
const { resolvePermissions, resolveFeatureFlag, isOnChatwootCloud } =
useSidebarContext();
const allowedToShow = computed(() => {
if (props.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
return true;
});
const { resolvePermissions, resolveFeatureFlag } = useSidebarContext();
const shouldRenderComponent = computed(() => {
return typeof props.component === 'function' || isVNode(props.component);
@@ -30,7 +22,6 @@ const shouldRenderComponent = computed(() => {
<!-- eslint-disable-next-line vue/no-root-v-if -->
<template>
<Policy
v-if="allowedToShow"
:permissions="resolvePermissions(to)"
:feature-flag="resolveFeatureFlag(to)"
as="li"
@@ -5,6 +5,7 @@ import { useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n';
import Avatar from 'next/avatar/Avatar.vue';
import SidebarProfileMenuStatus from './SidebarProfileMenuStatus.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import {
DropdownContainer,
@@ -21,14 +22,27 @@ defineOptions({
const { t } = useI18n();
const globalConfig = useMapGetter('globalConfig/get');
const currentUser = useMapGetter('getCurrentUser');
const currentUserAvailability = useMapGetter('getCurrentUserAvailability');
const accountId = useMapGetter('getCurrentAccountId');
const globalConfig = useMapGetter('globalConfig/get');
const isFeatureEnabledonAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const showChatSupport = computed(() => {
return (
isFeatureEnabledonAccount.value(
accountId.value,
FEATURE_FLAGS.CONTACT_CHATWOOT_SUPPORT_TEAM
) && globalConfig.value.chatwootInboxToken
);
});
const menuItems = computed(() => {
return [
{
show: !!globalConfig.value.chatwootInboxToken,
show: showChatSupport.value,
label: t('SIDEBAR_ITEMS.CONTACT_SUPPORT'),
icon: 'i-lucide-life-buoy',
click: () => {
@@ -14,12 +14,11 @@ const props = defineProps({
activeChild: { type: Object, default: undefined },
});
const { isAllowed, isOnChatwootCloud } = useSidebarContext();
const { isAllowed } = useSidebarContext();
const scrollableContainer = ref(null);
const accessibleItems = computed(() =>
props.children.filter(child => {
if (child.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
return child.to && isAllowed(child.to);
})
);
@@ -1,5 +1,4 @@
import { inject, provide } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
import { usePolicy } from 'dashboard/composables/usePolicy';
import { useRouter } from 'vue-router';
@@ -12,9 +11,8 @@ export function useSidebarContext() {
}
const router = useRouter();
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
const { checkFeatureAllowed, checkPermissions } = usePolicy();
const { shouldShow } = usePolicy();
const resolvePath = to => {
if (to) return router.resolve(to)?.path || '/';
@@ -31,11 +29,17 @@ export function useSidebarContext() {
return '';
};
const resolveInstallationType = to => {
if (to) return router.resolve(to)?.meta?.installationTypes || [];
return [];
};
const isAllowed = to => {
const permissions = resolvePermissions(to);
const featureFlag = resolveFeatureFlag(to);
const installationType = resolveInstallationType(to);
return checkPermissions(permissions) && checkFeatureAllowed(featureFlag);
return shouldShow(featureFlag, permissions, installationType);
};
return {
@@ -44,7 +48,6 @@ export function useSidebarContext() {
resolvePermissions,
resolveFeatureFlag,
isAllowed,
isOnChatwootCloud,
};
}
@@ -57,7 +57,7 @@ useEventListener(document.body, 'mouseup', onMouseUp);
useEventListener(document, 'keydown', onKeydown);
onMounted(() => {
if (onClose && typeof onClose === 'function') {
if (import.meta.env.DEV && onClose && typeof onClose === 'function') {
// eslint-disable-next-line no-console
console.warn(
"[DEPRECATED] The 'onClose' prop is deprecated. Please use the 'close' event instead."
@@ -28,10 +28,12 @@ export default {
},
},
created() {
// eslint-disable-next-line
console.warn(
'[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead'
);
if (import.meta.env.DEV) {
// eslint-disable-next-line
console.warn(
'[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead'
);
}
},
};
</script>
@@ -4,6 +4,7 @@ import Auth from '../../../api/auth';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
import { FEATURE_FLAGS } from '../../../featureFlags';
export default {
components: {
@@ -28,6 +29,7 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
showChangeAccountOption() {
if (this.globalConfig.createNewAccountFromDashboard) {
@@ -37,6 +39,14 @@ export default {
const { accounts = [] } = this.currentUser;
return accounts.length > 1;
},
showChatSupport() {
return (
this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.CONTACT_CHATWOOT_SUPPORT_TEAM
) && this.globalConfig.chatwootInboxToken
);
},
},
methods: {
handleProfileSettingClick(e, navigate) {
@@ -82,7 +92,7 @@ export default {
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
</woot-button>
</WootDropdownItem>
<WootDropdownItem v-if="globalConfig.chatwootInboxToken">
<WootDropdownItem v-if="showChatSupport">
<woot-button
variant="clear"
color-scheme="secondary"
@@ -15,17 +15,22 @@ const props = defineProps({
type: String,
default: null,
},
installationTypes: {
type: Array,
default: null,
},
});
const { checkFeatureAllowed, checkPermissions } = usePolicy();
const { shouldShow } = usePolicy();
const isFeatureAllowed = computed(() => checkFeatureAllowed(props.featureFlag));
const hasPermission = computed(() => checkPermissions(props.permissions));
const show = computed(() =>
shouldShow(props.featureFlag, props.permissions, props.installationTypes)
);
</script>
<!-- eslint-disable vue/no-root-v-if -->
<template>
<component :is="as" v-if="isFeatureAllowed && hasPermission">
<component :is="as" v-if="show">
<slot />
</component>
</template>
@@ -156,13 +156,14 @@ export default {
</slot>
<img
v-if="badgeSrc"
class="source-badge"
class="source-badge z-20"
:style="badgeStyle"
:src="`/integrations/channels/badges/${badgeSrc}.png`"
alt="Badge"
/>
<div
v-if="showStatusIndicator"
class="z-20"
:class="`source-badge user-online-status user-online-status--${status}`"
:style="statusStyle"
/>
@@ -95,9 +95,6 @@ export default {
activeInbox: 'getSelectedInbox',
accountId: 'getCurrentAccountId',
}),
bulkActionCheck() {
return !this.hideThumbnail && !this.hovered && !this.selected;
},
chatMetadata() {
return this.chat.meta || {};
},
@@ -182,10 +179,10 @@ export default {
router.push({ path });
},
onCardHover() {
onThumbnailHover() {
this.hovered = !this.hideThumbnail;
},
onCardLeave() {
onThumbnailLeave() {
this.hovered = false;
},
onSelectConversation(checked) {
@@ -249,28 +246,36 @@ export default {
'has-inbox-name': showInboxName,
'conversation-selected': selected,
}"
@mouseenter="onCardHover"
@mouseleave="onCardLeave"
@click="onCardClick"
@contextmenu="openContextMenu($event)"
>
<label v-if="hovered || selected" class="checkbox-wrapper" @click.stop>
<input
:value="selected"
:checked="selected"
class="checkbox"
type="checkbox"
@change="onSelectConversation($event.target.checked)"
<div
class="relative"
@mouseenter="onThumbnailHover"
@mouseleave="onThumbnailLeave"
>
<label
v-if="hovered || selected"
class="checkbox-wrapper absolute inset-0 z-20 backdrop-blur-[2px]"
@click.stop
>
<input
:value="selected"
:checked="selected"
class="checkbox"
type="checkbox"
@change="onSelectConversation($event.target.checked)"
/>
</label>
<Thumbnail
v-if="!hideThumbnail"
:src="currentContact.thumbnail"
:badge="inboxBadge"
:username="currentContact.name"
:status="currentContact.availability_status"
size="40px"
/>
</label>
<Thumbnail
v-if="bulkActionCheck"
:src="currentContact.thumbnail"
:badge="inboxBadge"
:username="currentContact.name"
:status="currentContact.availability_status"
size="40px"
/>
</div>
<div
class="px-0 py-3 border-b group-hover:border-transparent flex-1 border-n-slate-3 w-[calc(100%-40px)]"
>
@@ -400,7 +405,7 @@ export default {
}
.checkbox-wrapper {
@apply h-10 w-10 flex items-center justify-center rounded-full cursor-pointer mt-4 hover:bg-woot-100 dark:hover:bg-woot-800;
@apply h-10 w-10 flex items-center justify-center rounded-full cursor-pointer mt-4;
input[type='checkbox'] {
@apply m-0 cursor-pointer;
@@ -243,6 +243,15 @@ export default {
unreadMessageCount() {
return this.currentChat.unread_count || 0;
},
unreadMessageLabel() {
const count =
this.unreadMessageCount > 9 ? '9+' : this.unreadMessageCount;
const label =
this.unreadMessageCount > 1
? 'CONVERSATION.UNREAD_MESSAGES'
: 'CONVERSATION.UNREAD_MESSAGE';
return `${count} ${this.$t(label)}`;
},
isInstagramDM() {
return this.conversationType === 'instagram_direct_message';
},
@@ -492,12 +501,11 @@ export default {
<NextMessageList
v-if="showNextBubbles"
class="conversation-panel"
:read-messages="readMessages"
:un-read-messages="unReadMessages"
:current-user-id="currentUserId"
:first-unread-id="unReadMessages[0]?.id"
:is-an-email-channel="isAnEmailChannel"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:messages="currentChat ? currentChat.messages : []"
:messages="getMessages"
>
<template #beforeAll>
<transition name="slide-up">
@@ -507,15 +515,10 @@ export default {
</li>
</transition>
</template>
<template #beforeUnread>
<template #unreadBadge>
<li v-show="unreadMessageCount != 0" class="unread--toast">
<span>
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
{{
unreadMessageCount > 1
? $t('CONVERSATION.UNREAD_MESSAGES')
: $t('CONVERSATION.UNREAD_MESSAGE')
}}
{{ unreadMessageLabel }}
</span>
</li>
</template>
@@ -30,7 +30,7 @@ import {
import WhatsappTemplates from './WhatsappTemplates/Modal.vue';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin';
import { trimContent, debounce } from '@chatwoot/utils';
import { trimContent, debounce, getRecipients } from '@chatwoot/utils';
import wootConstants from 'dashboard/constants/globals';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
@@ -326,7 +326,8 @@ export default {
this.isAnEmailChannel ||
this.isAWebWidgetInbox ||
this.isAPIInbox ||
this.isAWhatsAppChannel
this.isAWhatsAppChannel ||
this.isATelegramChannel
);
},
isSignatureEnabledForInbox() {
@@ -388,7 +389,6 @@ export default {
watch: {
currentChat(conversation) {
const { can_reply: canReply } = conversation;
this.setCCAndToEmailsFromLastChat();
if (this.isOnPrivateNote) {
@@ -403,6 +403,19 @@ export default {
this.fetchAndSetReplyTo();
},
// When moving from one conversation to another, the store may not have the
// list of all the messages. A fetch is subsequently made to get the messages.
// However, this update does not trigger the `currentChat` watcher.
// We can add a deep watcher to it, but then, that would be too broad of a net to cast
// And would impact performance too. So we watch the messages directly.
// The watcher here is `deep` too, because the messages array is mutated and
// not replaced. So, a shallow watcher would not catch the change.
'currentChat.messages': {
handler() {
this.setCCAndToEmailsFromLastChat();
},
deep: true,
},
conversationIdByRoute(conversationId, oldConversationId) {
if (conversationId !== oldConversationId) {
this.setToDraft(oldConversationId, this.replyType);
@@ -989,45 +1002,20 @@ export default {
this.ccEmails = value.ccEmails;
},
setCCAndToEmailsFromLastChat() {
if (!this.lastEmail) return;
const {
content_attributes: { email: emailAttributes = {} },
} = this.lastEmail;
// Retrieve the email of the current conversation's sender
const conversationContact = this.currentChat?.meta?.sender?.email || '';
let cc = emailAttributes.cc ? [...emailAttributes.cc] : [];
let to = [];
const { email: inboxEmail, forward_to_email: forwardToEmail } =
this.inbox;
// there might be a situation where the current conversation will include a message from a third person,
// and the current conversation contact is in CC.
// This is an edge-case, reported here: CW-1511 [ONLY FOR INTERNAL REFERENCE]
// So we remove the current conversation contact's email from the CC list if present
if (cc.includes(conversationContact)) {
cc = cc.filter(email => email !== conversationContact);
}
// If the last incoming message sender is different from the conversation contact, add them to the "to"
// and add the conversation contact to the CC
if (!emailAttributes.from.includes(conversationContact)) {
to.push(...emailAttributes.from);
cc.push(conversationContact);
}
// Remove the conversation contact's email from the BCC list if present
let bcc = (emailAttributes.bcc || []).filter(
email => email !== conversationContact
const { cc, bcc, to } = getRecipients(
this.lastEmail,
conversationContact,
inboxEmail,
forwardToEmail
);
// Ensure only unique email addresses are in the CC list
bcc = [...new Set(bcc)];
cc = [...new Set(cc)];
to = [...new Set(to)];
this.toEmails = to.join(', ');
this.ccEmails = cc.join(', ');
this.bccEmails = bcc.join(', ');
this.toEmails = to.join(', ');
},
fetchAndSetReplyTo() {
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
@@ -9,26 +9,23 @@ export default {
type: Number,
required: true,
},
meetingData: {
type: Object,
default: () => ({}),
},
},
data() {
return { isLoading: false, dyteAuthToken: '', isSDKMounted: false };
},
computed: {
meetingLink() {
return buildDyteURL(this.meetingData.room_name, this.dyteAuthToken);
return buildDyteURL(this.dyteAuthToken);
},
},
methods: {
async joinTheCall() {
this.isLoading = true;
try {
const { data: { authResponse: { authToken } = {} } = {} } =
await DyteAPI.addParticipantToMeeting(this.messageId);
this.dyteAuthToken = authToken;
const { data: { token } = {} } = await DyteAPI.addParticipantToMeeting(
this.messageId
);
this.dyteAuthToken = token;
} catch (err) {
useAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
} finally {
@@ -40,10 +40,12 @@ export default {
},
emits: ['update:modelValue', 'input', 'blur'],
mounted() {
// eslint-disable-next-line
console.warn(
'[DEPRECATED] <WootInput> has be deprecated and will be removed soon. Please use v3/components/Form/Input.vue instead'
);
if (import.meta.env.DEV) {
// eslint-disable-next-line no-console
console.warn(
'[DEPRECATED] <WootInput> has be deprecated and will be removed soon. Please use v3/components/Form/Input.vue instead'
);
}
},
methods: {
onChange(e) {
@@ -0,0 +1,28 @@
import { ref, onBeforeUnmount } from 'vue';
export const useLiveRefresh = (callback, interval = 60000) => {
const timeoutId = ref(null);
const startRefetching = () => {
timeoutId.value = setTimeout(async () => {
await callback();
startRefetching();
}, interval);
};
const stopRefetching = () => {
if (timeoutId.value) {
clearTimeout(timeoutId.value);
timeoutId.value = null;
}
};
onBeforeUnmount(() => {
stopRefetching();
});
return {
startRefetching,
stopRefetching,
};
};
@@ -1,20 +1,31 @@
import { computed, unref } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
import { useAccount } from 'dashboard/composables/useAccount';
import { useConfig } from 'dashboard/composables/useConfig';
import {
getUserPermissions,
hasPermissions,
} from 'dashboard/helper/permissionsHelper';
import { PREMIUM_FEATURES } from 'dashboard/featureFlags';
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
export function usePolicy() {
const user = useMapGetter('getCurrentUser');
const isFeatureEnabled = useMapGetter('accounts/isFeatureEnabledonAccount');
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
const isACustomBrandedInstance = useMapGetter(
'globalConfig/isACustomBrandedInstance'
);
const { isEnterprise, enterprisePlanName } = useConfig();
const { accountId } = useAccount();
const getUserPermissionsForAccount = () => {
return getUserPermissions(user.value, accountId.value);
};
const checkFeatureAllowed = featureFlag => {
const isFeatureFlagEnabled = featureFlag => {
if (!featureFlag) return true;
return isFeatureEnabled.value(accountId.value, featureFlag);
};
@@ -25,5 +36,99 @@ export function usePolicy() {
return hasPermissions(requiredPermissions, userPermissions);
};
return { checkFeatureAllowed, checkPermissions };
const checkInstallationType = config => {
if (Array.isArray(config) && config.length > 0) {
const installationCheck = {
[INSTALLATION_TYPES.ENTERPRISE]: isEnterprise,
[INSTALLATION_TYPES.CLOUD]: isOnChatwootCloud.value,
[INSTALLATION_TYPES.COMMUNITY]: true,
};
return config.some(type => installationCheck[type]);
}
return true;
};
const isPremiumFeature = featureFlag => {
if (!featureFlag) return true;
return PREMIUM_FEATURES.includes(featureFlag);
};
const hasPremiumEnterprise = computed(() => {
if (isEnterprise) return enterprisePlanName !== 'community';
return true;
});
const shouldShow = (featureFlag, permissions, installationTypes) => {
const flag = unref(featureFlag);
const perms = unref(permissions);
const installation = unref(installationTypes);
// if the user does not have permissions or installation type is not supported
// return false;
// This supersedes everything
if (!checkPermissions(perms)) return false;
if (!checkInstallationType(installation)) return false;
if (isACustomBrandedInstance.value) {
// if this is a custom branded instance, we just use the feature flag as a reference
return isFeatureFlagEnabled(flag);
}
// if on cloud, we should if the feature is allowed
// or if the feature is a premium one like SLA to show a paywall
// the paywall should be managed by the individual component
if (isOnChatwootCloud.value) {
return isFeatureFlagEnabled(flag) || isPremiumFeature(flag);
}
if (isEnterprise) {
// in enterprise, if the feature is premium but they don't have an enterprise plan
// we should it anyway this is to show upsells on enterprise regardless of the feature flag
// Feature flag is only honored if they have a premium plan
//
// In case they have a premium plan, the check on feature flag alone is enough
// because the second condition will always be false
// That means once subscribed, the feature can be disabled by the admin
//
// the paywall should be managed by the individual component
return (
isFeatureFlagEnabled(flag) ||
(isPremiumFeature(flag) && !hasPremiumEnterprise.value)
);
}
// default to true
return true;
};
const shouldShowPaywall = featureFlag => {
const flag = unref(featureFlag);
if (!flag) return false;
if (isACustomBrandedInstance.value) {
// custom branded instances never show paywall
return false;
}
if (isPremiumFeature(flag)) {
if (isOnChatwootCloud.value) {
return !isFeatureFlagEnabled(flag);
}
if (isEnterprise) {
return !hasPremiumEnterprise.value;
}
}
return false;
};
return {
checkPermissions,
shouldShowPaywall,
shouldShow,
};
}
@@ -0,0 +1,5 @@
export const INSTALLATION_TYPES = {
CLOUD: 'cloud',
ENTERPRISE: 'enterprise',
COMMUNITY: 'community',
};
+9
View File
@@ -34,4 +34,13 @@ export const FEATURE_FLAGS = {
CUSTOM_ROLES: 'custom_roles',
CHATWOOT_V4: 'chatwoot_v4',
REPORT_V4: 'report_v4',
CONTACT_CHATWOOT_SUPPORT_TEAM: 'contact_chatwoot_support_team',
};
export const PREMIUM_FEATURES = [
FEATURE_FLAGS.SLA,
FEATURE_FLAGS.CAPTAIN,
FEATURE_FLAGS.CUSTOM_ROLES,
FEATURE_FLAGS.AUDIT_LOGS,
FEATURE_FLAGS.HELP_CENTER,
];
@@ -152,7 +152,10 @@ export class DashboardAudioNotificationHelper {
const shouldPlayAudio = [];
if (audioAlertType.includes(EVENT_TYPES.ASSIGNED)) {
if (
audioAlertType.includes(EVENT_TYPES.ASSIGNED) ||
audioAlertType.includes('mine')
) {
shouldPlayAudio.push(assignedToMe);
}
if (audioAlertType.includes(EVENT_TYPES.UNASSIGNED)) {
@@ -8,6 +8,7 @@ import DashboardAudioNotificationHelper from './AudioAlerts/DashboardAudioNotifi
import { emitter } from 'shared/helpers/mitt';
export const initializeAnalyticsEvents = () => {
AnalyticsHelper.init();
emitter.on(ANALYTICS_IDENTITY, ({ user }) => {
AnalyticsHelper.identify(user);
});
@@ -99,6 +99,9 @@
},
"fallback": {
"CONTENT": "has shared a url"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -54,6 +54,7 @@
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
"NO_RESPONSE": "No response",
"RESPONSE": "Response",
"RATING_TITLE": "Rating",
"FEEDBACK_TITLE": "Feedback",
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
@@ -352,5 +353,11 @@
"ONE": "{user} is typing",
"TWO": "{user} and {secondUser} are typing",
"MULTIPLE": "{user} and {count} others are typing"
},
"COPILOT": {
"TRY_THESE_PROMPTS": "Try these prompts"
},
"GALLERY_VIEW": {
"ERROR_DOWNLOADING": "Unable to download attachment. Please try again"
}
}
@@ -0,0 +1,77 @@
import advancedFilters from './advancedFilters.json';
import agentBots from './agentBots.json';
import agentMgmt from './agentMgmt.json';
import attributesMgmt from './attributesMgmt.json';
import auditLogs from './auditLogs.json';
import automation from './automation.json';
import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import components from './components.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmt from './csatMgmt.json';
import customRole from './customRole.json';
import datePicker from './datePicker.json';
import emoji from './emoji.json';
import general from './general.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inbox from './inbox.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
import labelsMgmt from './labelsMgmt.json';
import login from './login.json';
import macros from './macros.json';
import report from './report.json';
import resetPassword from './resetPassword.json';
import search from './search.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
export default {
...advancedFilters,
...agentBots,
...agentMgmt,
...attributesMgmt,
...auditLogs,
...automation,
...bulkActions,
...campaign,
...cannedMgmt,
...chatlist,
...components,
...contact,
...contactFilters,
...conversation,
...csatMgmt,
...customRole,
...datePicker,
...emoji,
...general,
...generalSettings,
...helpCenter,
...inbox,
...inboxMgmt,
...integrationApps,
...integrations,
...labelsMgmt,
...login,
...macros,
...report,
...resetPassword,
...search,
...setNewPassword,
...settings,
...signup,
...sla,
...teamsSettings,
...whatsappTemplates,
};
@@ -309,7 +309,217 @@
"SEND_MESSAGE": "Send message...",
"LOADER": "Captain is thinking",
"YOU": "You",
"USE": "Use this"
"USE": "Use this",
"RESET": "Reset"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
"AVAILABLE_ON": "Captain is not available on the free plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"UPGRADE_NOW": "Upgrade now",
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
},
"BANNER": {
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
},
"FORM": {
"CANCEL": "Cancel",
"CREATE": "Create",
"EDIT": "Update"
},
"ASSISTANTS": {
"HEADER": "Assistants",
"ADD_NEW": "Create a new assistant",
"DELETE": {
"TITLE": "Are you sure to delete the assistant?",
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
},
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
"CREATE": {
"TITLE": "Create an assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully created",
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
},
"FORM": {
"NAME": {
"LABEL": "Assistant Name",
"PLACEHOLDER": "Enter a name for the assistant",
"ERROR": "Please provide a name for the assistant"
},
"DESCRIPTION": {
"LABEL": "Assistant Description",
"PLACEHOLDER": "Describe how and where this assistant will be used",
"ERROR": "A description is required"
},
"PRODUCT_NAME": {
"LABEL": "Product Name",
"PLACEHOLDER": "Enter the name of the product this assistant is designed for",
"ERROR": "The product name is required"
},
"FEATURES": {
"TITLE": "Features",
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions."
}
},
"EDIT": {
"TITLE": "Update the assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the assistant, please try again."
},
"OPTIONS": {
"EDIT_ASSISTANT": "Edit Assistant",
"DELETE_ASSISTANT": "Delete Assistant",
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
},
"EMPTY_STATE": {
"TITLE": "No assistants available",
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations."
}
},
"DOCUMENTS": {
"HEADER": "Documents",
"ADD_NEW": "Create a new document",
"RELATED_RESPONSES": {
"TITLE": "Related FAQs",
"DESCRIPTION": "These FAQs are generated directly from the document."
},
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": {
"TITLE": "Add a document",
"SUCCESS_MESSAGE": "The document has been successfully created",
"ERROR_MESSAGE": "There was an error creating the document, please try again."
},
"FORM": {
"URL": {
"LABEL": "URL",
"PLACEHOLDER": "Enter the URL of the document",
"ERROR": "Please provide a valid URL for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
}
},
"DELETE": {
"TITLE": "Are you sure to delete the document?",
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The document has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "View Related Responses",
"DELETE_DOCUMENT": "Delete Document"
},
"EMPTY_STATE": {
"TITLE": "No documents available",
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant."
}
},
"RESPONSES": {
"HEADER": "FAQs",
"ADD_NEW": "Create new FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversation #{id}"
},
"DELETE": {
"TITLE": "Are you sure to delete the FAQ?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "FAQ deleted successfully",
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
},
"FILTER": {
"ASSISTANT": "Assistant: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "All"
},
"STATUS": {
"TITLE": "Status",
"PENDING": "Pending",
"APPROVED": "Approved",
"ALL": "All"
},
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
"CREATE": {
"TITLE": "Add an FAQ",
"SUCCESS_MESSAGE": "The response has been added successfully.",
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
},
"FORM": {
"QUESTION": {
"LABEL": "Question",
"PLACEHOLDER": "Enter the question here",
"ERROR": "Please provide a valid question."
},
"ANSWER": {
"LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
}
},
"EDIT": {
"TITLE": "Update the FAQ",
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
},
"OPTIONS": {
"APPROVE": "Mark as approved",
"EDIT_RESPONSE": "Edit FAQ",
"DELETE_RESPONSE": "Delete FAQ"
},
"EMPTY_STATE": {
"TITLE": "No FAQs Found",
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
}
},
"INBOXES": {
"HEADER": "Connected Inboxes",
"ADD_NEW": "Connect a new inbox",
"OPTIONS": {
"DISCONNECT": "Disconnect"
},
"DELETE": {
"TITLE": "Are you sure to disconnect the inbox?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
},
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
"CREATE": {
"TITLE": "Connect an Inbox",
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
},
"FORM": {
"INBOX": {
"LABEL": "Inbox",
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
"ERROR": "An inbox selection is required."
}
},
"EMPTY_STATE": {
"TITLE": "No Connected Inboxes",
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
}
}
}
}
@@ -23,13 +23,13 @@
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@@ -69,6 +69,50 @@
"MONTH": "Month",
"YEAR": "Year"
},
"GROUP_BY_DAY_OPTIONS": [
{
"id": 1,
"groupBy": "Day"
}
],
"GROUP_BY_WEEK_OPTIONS": [
{
"id": 1,
"groupBy": "Day"
},
{
"id": 2,
"groupBy": "Week"
}
],
"GROUP_BY_MONTH_OPTIONS": [
{
"id": 1,
"groupBy": "Day"
},
{
"id": 2,
"groupBy": "Week"
},
{
"id": 3,
"groupBy": "Month"
}
],
"GROUP_BY_YEAR_OPTIONS": [
{
"id": 2,
"groupBy": "Week"
},
{
"id": 3,
"groupBy": "Month"
},
{
"id": 4,
"groupBy": "Year"
}
],
"BUSINESS_HOURS": "Business Hours",
"FILTER_ACTIONS": {
"CLEAR_FILTER": "Clear filter",
@@ -80,6 +124,7 @@
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agents name to learn more.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
@@ -101,13 +146,13 @@
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@@ -174,7 +219,7 @@
"NAME": "Resolution Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@@ -214,6 +259,7 @@
},
"INBOX_REPORTS": {
"HEADER": "Inbox Overview",
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
@@ -235,13 +281,13 @@
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "First Response Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)""TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
"TOOLTIP_TEXT": "Resolution Time is {metricValue} (based on {conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@@ -281,6 +327,7 @@
},
"TEAM_REPORTS": {
"HEADER": "Team Overview",
"DESCRIPTION": "Get a snapshot of your teams performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_TEAM_REPORTS": "Download team reports",
@@ -416,8 +463,6 @@
"NO_CONVERSATIONS": "No conversations",
"CONVERSATION": "{count} conversation",
"CONVERSATIONS": "{count} conversations",
"CONVERSATION": "{count} conversation",
"CONVERSATIONS": "{count} conversations",
"DOWNLOAD_REPORT": "Download report"
},
"AGENT_CONVERSATIONS": {
@@ -496,5 +541,15 @@
},
"VIEW_DETAILS": "View Details"
}
},
"SUMMARY_REPORTS": {
"INBOX": "Inbox",
"AGENT": "Agent",
"TEAM": "Team",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
"RESOLUTION_COUNT": "Resolution Count",
"CONVERSATIONS": "No. of conversations"
}
}
}
@@ -11,7 +11,10 @@
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages"
},
"VIEW_MORE": "View more",
"LOAD_MORE": "Load more",
"SEARCHING_DATA": "Searching",
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
@@ -21,6 +24,7 @@
"READ_MORE": "Read more",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email"
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
}
}
@@ -194,7 +194,7 @@
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "Change",
"CHANGE_ACCOUNTS": "Switch account",
"SWITCH_WORKSPACE": "Switch workspace",
"SWITCH_ACCOUNT": "Switch account",
"CONTACT_SUPPORT": "Contact support",
"SELECTOR_SUBTITLE": "Select an account from the following list",
"PROFILE_SETTINGS": "Profile settings",
@@ -263,6 +263,9 @@
"SETTINGS": "Settings",
"CONTACTS": "Contacts",
"CAPTAIN": "Captain",
"CAPTAIN_ASSISTANTS": "Assistants",
"CAPTAIN_DOCUMENTS": "Documents",
"CAPTAIN_RESPONSES": "FAQs",
"HOME": "Home",
"AGENTS": "Agents",
"AGENT_BOTS": "Bots",
@@ -324,15 +327,27 @@
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
"CURRENT_PLAN": {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses"
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses",
"SEAT_COUNT": "Number of seats",
"RENEWS_ON": "Renews on"
},
"VIEW_PRICING": "View Pricing",
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
},
"CAPTAIN": {
"TITLE": "Captain",
"DESCRIPTION": "Manage usage and credits for Captain AI.",
"BUTTON_TXT": "Buy more credits",
"DOCUMENTS": "Documents",
"RESPONSES": "Responses",
"UPGRADE": "Captain is not available on the free plan, upgrade now to get access to assistants, copilot and more."
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
@@ -1,74 +1,74 @@
{
"AUDIT_LOGS": {
"HEADER": "سجلات التدقيق",
"HEADER_BTN_TXT": "إضافة سجلات التدقيق",
"LOADING": "جارٍ جلب سجلات التدقيق",
"DESCRIPTION": "سجلات مراجعة الحسابات تحتفظ بسجل للأنشطة في حسابك، مما يسمح لك بتتبع ومراجعة حسابك أو فريقك أو خدماتك.",
"LEARN_MORE": "معرفة المزيد عن سجلات المراجعة",
"SEARCH_404": "لا توجد عناصر مطابقة لهذا الاستعلام",
"SIDEBAR_TXT": "<p><b>سجلات التدقيق</b> </p><p> سجلات التدقيق هي مسارات للأحداث والإجراءات في نظام Chatwoot. </p>",
"LIST": {
"404": "لا توجد سجلات تدقيق متاحة في هذا الحساب.",
"TITLE": "إدارة سجلات التدقيق",
"DESC": "سجلات التدقيق هي مسارات للأحداث والإجراءات في نظام Chatwoot.",
"TABLE_HEADER": [
"المستخدم",
"الإجراء",
"عنوان IP"
]
},
"API": {
"SUCCESS_MESSAGE": "تم استرجاع سجلات التدقيق بنجاح",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
},
"DEFAULT_USER": "النظام",
"AUTOMATION_RULE": {
"ADD": "{agentName} أنشأ قاعدة أتمتة جديدة (#{id})",
"EDIT": "{agentName} قام بتحديث قاعدة أتمتة (#{id})",
"DELETE": "{agentName} حذف قاعدة أتمتة (#{id})"
},
"ACCOUNT_USER": {
"ADD": "{agentName} دعا {invitee} إلى الحساب كـ {role}",
"EDIT": {
"SELF": "%{agentName} غير %{attributes} الخاصة به إلى %{values}",
"OTHER": "%{agentName} غير %{attributes} لـ %{user} إلى %{values}",
"DELETED": "%{agentName} غير %{attributes} لـ %{user} إلى %{values}"
}
},
"INBOX": {
"ADD": "{agentName} أنشأ صندوق وارد جديد (#{id})",
"EDIT": "{agentName} قام بتحديث صندوق الوارد (#{id})",
"DELETE": "{agentName} حذف صندوق الوارد (#{id})"
},
"WEBHOOK": {
"ADD": "{agentName} أنشأ Webhook جديد (#{id})",
"EDIT": "{agentName} قام بتحديث Webhook (#{id})",
"DELETE": "{agentName} حذف Webhook (#{id})"
},
"USER_ACTION": {
"SIGN_IN": "{agentName} قام بتسجيل الدخول",
"SIGN_OUT": "{agentName} قام بتسجيل الخروج"
},
"TEAM": {
"ADD": "{agentName} أنشأ فريق جديد (#{id})",
"EDIT": "{agentName} قام بتحديث الفريق (#{id})",
"DELETE": "{agentName} حذف الفريق (#{id})"
},
"MACRO": {
"ADD": "{agentName} أنشأ ماكرو جديد (#{id})",
"EDIT": "{agentName} قام بتحديث ماكرو (#{id})",
"DELETE": "{agentName} حذف ماكرو (#{id})"
},
"INBOX_MEMBER": {
"ADD": "{agentName} أضاف {user} إلى صندوق الوارد (#{inbox_id})",
"REMOVE": "{agentName} أزال {user} من صندوق الوارد (#{inbox_id})"
},
"TEAM_MEMBER": {
"ADD": "{agentName} أضاف {user} إلى الفريق (#{team_id})",
"REMOVE": "{agentName} أزال {user} من الفريق (#{team_id})"
},
"ACCOUNT": {
"EDIT": "{agentName} قام بتحديث إعدادات الحساب (#{id})"
}
"AUDIT_LOGS": {
"HEADER": "سجلات التدقيق",
"HEADER_BTN_TXT": "إضافة سجلات التدقيق",
"LOADING": "جارٍ جلب سجلات التدقيق",
"DESCRIPTION": "سجلات مراجعة الحسابات تحتفظ بسجل للأنشطة في حسابك، مما يسمح لك بتتبع ومراجعة حسابك أو فريقك أو خدماتك.",
"LEARN_MORE": "معرفة المزيد عن سجلات المراجعة",
"SEARCH_404": "لا توجد عناصر مطابقة لهذا الاستعلام",
"SIDEBAR_TXT": "<p><b>سجلات التدقيق</b> </p><p> سجلات التدقيق هي مسارات للأحداث والإجراءات في نظام Chatwoot. </p>",
"LIST": {
"404": "لا توجد سجلات تدقيق متاحة في هذا الحساب.",
"TITLE": "إدارة سجلات التدقيق",
"DESC": "سجلات التدقيق هي مسارات للأحداث والإجراءات في نظام Chatwoot.",
"TABLE_HEADER": {
"ACTIVITY": "الأنشطة",
"TIME": "الوقت",
"IP_ADDRESS": "عنوان IP"
}
}
},
"API": {
"SUCCESS_MESSAGE": "تم استرجاع سجلات التدقيق بنجاح",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
},
"DEFAULT_USER": "النظام",
"AUTOMATION_RULE": {
"ADD": "{agentName} أنشأ قاعدة أتمتة جديدة (##{id})",
"EDIT": "{agentName} قام بتحديث قاعدة أتمتة (##{id})",
"DELETE": "{agentName} حذف قاعدة أتمتة (##{id})"
},
"ACCOUNT_USER": {
"ADD": "{agentName} دعا {invitee} إلى الحساب كـ {role}",
"EDIT": {
"SELF": "{agentName} غير {attributes} الخاصة به إلى {values}",
"OTHER": "{agentName} غير {attributes} لـ {user} إلى {values}",
"DELETED": "{agentName} غير {attributes} لـ %{user} إلى {values}"
}
},
"INBOX": {
"ADD": "{agentName} أنشأ صندوق وارد جديد (##{id})",
"EDIT": "{agentName} قام بتحديث صندوق الوارد (##{id})",
"DELETE": "{agentName} حذف صندوق الوارد (##{id})"
},
"WEBHOOK": {
"ADD": "{agentName} أنشأ Webhook جديد (##{id})",
"EDIT": "{agentName} قام بتحديث Webhook (##{id})",
"DELETE": "{agentName} حذف Webhook (##{id})"
},
"USER_ACTION": {
"SIGN_IN": "{agentName} قام بتسجيل الدخول",
"SIGN_OUT": "{agentName} قام بتسجيل الخروج"
},
"TEAM": {
"ADD": "{agentName} أنشأ فريق جديد (##{id})",
"EDIT": "{agentName} قام بتحديث الفريق (##{id})",
"DELETE": "{agentName} حذف الفريق (##{id})"
},
"MACRO": {
"ADD": "{agentName} أنشأ ماكرو جديد (##{id})",
"EDIT": "{agentName} قام بتحديث ماكرو (##{id})",
"DELETE": "{agentName} حذف ماكرو (##{id})"
},
"INBOX_MEMBER": {
"ADD": "{agentName} أضاف {user} إلى صندوق الوارد (##{inbox_id})",
"REMOVE": "{agentName} أزال {user} من صندوق الوارد (##{inbox_id})"
},
"TEAM_MEMBER": {
"ADD": "{agentName} أضاف {user} إلى الفريق (##{team_id})",
"REMOVE": "{agentName} أزال {user} من الفريق (##{team_id})"
},
"ACCOUNT": {
"EDIT": "{agentName} قام بتحديث إعدادات الحساب (##{id})"
}
}
}
@@ -99,6 +99,9 @@
},
"fallback": {
"CONTENT": "قام بمشاركة رابط"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -54,6 +54,7 @@
"SUCCESS_DELETE_MESSAGE": "تم حذف الرسالة بنجاح",
"FAIL_DELETE_MESSSAGE": "تعذر حذف الرسالة! حاول مرة أخرى",
"NO_RESPONSE": "لا توجد استجابة",
"RESPONSE": "Response",
"RATING_TITLE": "التقييم",
"FEEDBACK_TITLE": "الملاحظات",
"REPLY_MESSAGE_NOT_FOUND": "الرسالة غير متوفرة",
@@ -133,7 +134,7 @@
"FAILED": "تعذر تعيين الوكيل. الرجاء المحاولة مرة أخرى."
},
"LABEL_ASSIGNMENT": {
"SUCCESFUL": "تعيين تسمية #{labelName} لمعرف المحادثة {conversationId}",
"SUCCESFUL": "تعيين تسمية ##{labelName} لمعرف المحادثة {conversationId}",
"FAILED": "تعذر تعيين التسمية. الرجاء المحاولة مرة أخرى."
},
"TEAM_ASSIGNMENT": {
@@ -352,5 +353,11 @@
"ONE": "{user} is typing",
"TWO": "{user} and {secondUser} are typing",
"MULTIPLE": "{user} and {count} others are typing"
},
"COPILOT": {
"TRY_THESE_PROMPTS": "Try these prompts"
},
"GALLERY_VIEW": {
"ERROR_DOWNLOADING": "Unable to download attachment. Please try again"
}
}
}
@@ -8,13 +8,18 @@ import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import components from './components.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmt from './csatMgmt.json';
import customRole from './customRole.json';
import datePicker from './datePicker.json';
import emoji from './emoji.json';
import general from './general.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inbox from './inbox.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
@@ -27,6 +32,7 @@ import search from './search.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
@@ -41,13 +47,18 @@ export default {
...campaign,
...cannedMgmt,
...chatlist,
...components,
...contact,
...contactFilters,
...conversation,
...csatMgmt,
...customRole,
...datePicker,
...emoji,
...general,
...generalSettings,
...helpCenter,
...inbox,
...inboxMgmt,
...integrationApps,
...integrations,
@@ -60,6 +71,7 @@ export default {
...setNewPassword,
...settings,
...signup,
...sla,
...teamsSettings,
...whatsappTemplates,
};
@@ -56,7 +56,7 @@
"BUTTON_TEXT": "قطع الاتصال"
},
"SIDEBAR_DESCRIPTION": {
"DIALOGFLOW": "تدفق الحوار هو منصة لفهم اللغة الطبيعية التي تجعل من السهل تصميم ودمج واجهة المستخدم للمحادثة في تطبيق الهاتف المحمول الخاص بك، تطبيق الويب، الجهاز، البوت، نظام الاستجابة الصوتية التفاعلي، وما إلى ذلك. <br /> <br /> تكامل تدفق الحوار مع {installationName} يسمح لك بتكوين بوت تدفق الحوار مع صناديق الوارد الخاصة بك والذي يتيح للبوت التعامل مع الاستفسارات في البداية وتسليمها إلى وكيل عند الحاجة. ويمكن استخدام تدفق البيانات لتأهيل الخيوط وتقليل عبء العمل الملقى على عاتق الوكلاء عن طريق طرح الأسئلة المتكررة وما إلى ذلك. <br /> <br /> لإضافة تدفق الحوار، تحتاج إلى إنشاء حساب خدمة في وحدة تحكم مشروع جوجل الخاص بك ومشاركة بيانات الاعتماد. يرجى الرجوع إلى مستندات Dialogflow للحصول على مزيد من المعلومات."
"DIALOGFLOW": "Dialogflow is a natural language understanding platform that makes it easy to design and integrate a conversational user interface into your mobile app, web application, device, bot, interactive voice response system, and so on. <br /> <br /> Dialogflow integration with {installationName} allows you to configure a Dialogflow bot with your inboxes which lets the bot handle the queries initially and hand them over to an agent when needed. Dialogflow can be used to qualifying the leads, reduce the workload of agents by providing frequently asked questions etc. <br /> <br /> To add Dialogflow, you need to create a Service Account in your Google project console and share the credentials. Please refer to the Dialogflow docs for more information."
}
}
}
}
@@ -235,7 +235,7 @@
"ERROR": "حدث خطأ أثناء جلب المشكلات من Linear، الرجاء المحاولة مرة أخرى",
"LINK_SUCCESS": "تم ربط المشكلة بنجاح",
"LINK_ERROR": "حدث خطأ أثناء ربط المشكلة، الرجاء المحاولة مرة أخرى",
"LINK_TITLE": "محادثة (#{conversationId}) مع {name}"
"LINK_TITLE": "Conversation (#{conversationId}) with {name}"
},
"ADD_OR_LINK": {
"TITLE": "إنشاء/رابط مشكلة في Linear",
@@ -294,7 +294,7 @@
"PRIORITY": "الأولوية",
"ASSIGNEE": "المكلَّف",
"LABELS": "الوسوم",
"CREATED_AT": "تم إنشاؤها في {createdAt}"
"CREATED_AT": "Created at {createdAt}"
},
"UNLINK": {
"TITLE": "إلغاء الربط",
@@ -309,7 +309,217 @@
"SEND_MESSAGE": "إرسال الرسالة...",
"LOADER": "Captain is thinking",
"YOU": "أنت",
"USE": "Use this"
"USE": "Use this",
"RESET": "Reset"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
"AVAILABLE_ON": "Captain is not available on the free plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"UPGRADE_NOW": "Upgrade now",
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
},
"BANNER": {
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
},
"FORM": {
"CANCEL": "إلغاء",
"CREATE": "إنشاء",
"EDIT": "تحديث"
},
"ASSISTANTS": {
"HEADER": "Assistants",
"ADD_NEW": "Create a new assistant",
"DELETE": {
"TITLE": "Are you sure to delete the assistant?",
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
"CONFIRM": "نعم، احذف",
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
},
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
"CREATE": {
"TITLE": "Create an assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully created",
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
},
"FORM": {
"NAME": {
"LABEL": "Assistant Name",
"PLACEHOLDER": "Enter a name for the assistant",
"ERROR": "Please provide a name for the assistant"
},
"DESCRIPTION": {
"LABEL": "Assistant Description",
"PLACEHOLDER": "Describe how and where this assistant will be used",
"ERROR": "A description is required"
},
"PRODUCT_NAME": {
"LABEL": "Product Name",
"PLACEHOLDER": "Enter the name of the product this assistant is designed for",
"ERROR": "The product name is required"
},
"FEATURES": {
"TITLE": "الخصائص",
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions."
}
},
"EDIT": {
"TITLE": "Update the assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the assistant, please try again."
},
"OPTIONS": {
"EDIT_ASSISTANT": "Edit Assistant",
"DELETE_ASSISTANT": "Delete Assistant",
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
},
"EMPTY_STATE": {
"TITLE": "No assistants available",
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations."
}
},
"DOCUMENTS": {
"HEADER": "Documents",
"ADD_NEW": "Create a new document",
"RELATED_RESPONSES": {
"TITLE": "Related FAQs",
"DESCRIPTION": "These FAQs are generated directly from the document."
},
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": {
"TITLE": "Add a document",
"SUCCESS_MESSAGE": "The document has been successfully created",
"ERROR_MESSAGE": "There was an error creating the document, please try again."
},
"FORM": {
"URL": {
"LABEL": "الرابط",
"PLACEHOLDER": "Enter the URL of the document",
"ERROR": "Please provide a valid URL for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
}
},
"DELETE": {
"TITLE": "Are you sure to delete the document?",
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
"CONFIRM": "نعم، احذف",
"SUCCESS_MESSAGE": "The document has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "View Related Responses",
"DELETE_DOCUMENT": "Delete Document"
},
"EMPTY_STATE": {
"TITLE": "No documents available",
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant."
}
},
"RESPONSES": {
"HEADER": "FAQs",
"ADD_NEW": "Create new FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversation #{id}"
},
"DELETE": {
"TITLE": "Are you sure to delete the FAQ?",
"DESCRIPTION": "",
"CONFIRM": "نعم، احذف",
"SUCCESS_MESSAGE": "FAQ deleted successfully",
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
},
"FILTER": {
"ASSISTANT": "Assistant: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "الكل"
},
"STATUS": {
"TITLE": "الحالة",
"PENDING": "معلق",
"APPROVED": "Approved",
"ALL": "الكل"
},
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
"CREATE": {
"TITLE": "Add an FAQ",
"SUCCESS_MESSAGE": "The response has been added successfully.",
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
},
"FORM": {
"QUESTION": {
"LABEL": "Question",
"PLACEHOLDER": "Enter the question here",
"ERROR": "Please provide a valid question."
},
"ANSWER": {
"LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
}
},
"EDIT": {
"TITLE": "Update the FAQ",
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
},
"OPTIONS": {
"APPROVE": "Mark as approved",
"EDIT_RESPONSE": "Edit FAQ",
"DELETE_RESPONSE": "Delete FAQ"
},
"EMPTY_STATE": {
"TITLE": "No FAQs Found",
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
}
},
"INBOXES": {
"HEADER": "Connected Inboxes",
"ADD_NEW": "Connect a new inbox",
"OPTIONS": {
"DISCONNECT": "قطع الاتصال"
},
"DELETE": {
"TITLE": "Are you sure to disconnect the inbox?",
"DESCRIPTION": "",
"CONFIRM": "نعم، احذف",
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
},
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
"CREATE": {
"TITLE": "Connect an Inbox",
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
},
"FORM": {
"INBOX": {
"LABEL": "صندوق الوارد",
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
"ERROR": "An inbox selection is required."
}
},
"EMPTY_STATE": {
"TITLE": "No Connected Inboxes",
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
}
}
}
}
}
@@ -3,7 +3,7 @@
"TITLE": "تسجيل الدخول إلى Chatwoot",
"EMAIL": {
"LABEL": "البريد الإلكتروني",
"PLACEHOLDER": "مثلاً: someone{'@'}example.com",
"PLACEHOLDER": "example{'@'}companyname.com",
"ERROR": "الرجاء إدخال عنوان بريد إلكتروني صحيح"
},
"PASSWORD": {
@@ -24,4 +24,4 @@
"CREATE_NEW_ACCOUNT": "إنشاء حساب جديد",
"SUBMIT": "تسجيل الدخول"
}
}
}
@@ -124,6 +124,7 @@
},
"AGENT_REPORTS": {
"HEADER": "نظرة عامة للوكلاء",
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agents name to learn more.",
"LOADING_CHART": "جاري جلب بيانات الرسم البياني...",
"NO_ENOUGH_DATA": "لم يتم جمع بيانات بقدر كافي لإنشاء التقرير، الرجاء المحاولة مرة أخرى لاحقاً.",
"DOWNLOAD_AGENT_REPORTS": "تنزيل تقارير الوكيل",
@@ -258,6 +259,7 @@
},
"INBOX_REPORTS": {
"HEADER": "نظرة عامة على صندوق الوارد",
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
"LOADING_CHART": "تحميل بيانات الرسم البياني...",
"NO_ENOUGH_DATA": "لم يتم جمع بيانات بقدر كافي لإنشاء التقرير، الرجاء المحاولة مرة أخرى لاحقاً.",
"DOWNLOAD_INBOX_REPORTS": "تحميل تقارير صندوق الوارد",
@@ -325,6 +327,7 @@
},
"TEAM_REPORTS": {
"HEADER": "نظرة عامة للفريق",
"DESCRIPTION": "Get a snapshot of your teams performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
"LOADING_CHART": "تحميل بيانات الرسم البياني...",
"NO_ENOUGH_DATA": "لم يتم جمع بيانات بقدر كافي لإنشاء التقرير، الرجاء المحاولة مرة أخرى لاحقاً.",
"DOWNLOAD_TEAM_REPORTS": "تحميل تقارير الفريق",
@@ -538,5 +541,15 @@
},
"VIEW_DETAILS": "View Details"
}
},
"SUMMARY_REPORTS": {
"INBOX": "صندوق الوارد",
"AGENT": "وكيل الدعم",
"TEAM": "الفريق",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
"RESOLUTION_COUNT": "عدد مرات الإغلاق",
"CONVERSATIONS": "عدد المحادثات"
}
}
}
@@ -11,7 +11,10 @@
"CONVERSATIONS": "المحادثات",
"MESSAGES": "الرسائل"
},
"VIEW_MORE": "View more",
"LOAD_MORE": "Load more",
"SEARCHING_DATA": "جاري البحث",
"LOADING_DATA": "جار التحميل",
"EMPTY_STATE": "لم يتم العثور على {item} للطلب '{query}'",
"EMPTY_STATE_FULL": "لم يتم العثور على نتائج للطلب '{query}'",
"PLACEHOLDER_KEYBINDING": "/ للتركيز",
@@ -21,6 +24,7 @@
"READ_MORE": "اقرأ المزيد",
"WROTE": "كتب:",
"FROM": "من",
"EMAIL": "البريد الإلكتروني"
"EMAIL": "البريد الإلكتروني",
"EMAIL_SUBJECT": "الموضوع"
}
}
@@ -194,7 +194,7 @@
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "يتغيرون",
"CHANGE_ACCOUNTS": "Switch account",
"SWITCH_WORKSPACE": "Switch workspace",
"SWITCH_ACCOUNT": "Switch account",
"CONTACT_SUPPORT": "Contact support",
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
"PROFILE_SETTINGS": "Profile settings",
@@ -263,6 +263,9 @@
"SETTINGS": "الإعدادات",
"CONTACTS": "جهات الاتصال",
"CAPTAIN": "قائد",
"CAPTAIN_ASSISTANTS": "Assistants",
"CAPTAIN_DOCUMENTS": "Documents",
"CAPTAIN_RESPONSES": "FAQs",
"HOME": "الرئيسية",
"AGENTS": "وكيل الدعم",
"AGENT_BOTS": "الروبوتات",
@@ -324,15 +327,27 @@
},
"BILLING_SETTINGS": {
"TITLE": "الفواتير",
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
"CURRENT_PLAN": {
"TITLE": "الباقة الحالية",
"PLAN_NOTE": "أنت مشترك حاليا في باقة**{plan}** مع تراخيص **{quantity}**"
"PLAN_NOTE": "أنت مشترك حاليا في باقة**{plan}** مع تراخيص **{quantity}**",
"SEAT_COUNT": "Number of seats",
"RENEWS_ON": "Renews on"
},
"VIEW_PRICING": "View Pricing",
"MANAGE_SUBSCRIPTION": {
"TITLE": "إدارة الاشتراك الخاص بك",
"DESCRIPTION": "عرض فواتيرك السابقة، تحرير تفاصيل الفوترة الخاصة بك، أو إلغاء اشتراكك.",
"BUTTON_TXT": "الذهاب إلى بوابة الفوترة"
},
"CAPTAIN": {
"TITLE": "قائد",
"DESCRIPTION": "Manage usage and credits for Captain AI.",
"BUTTON_TXT": "Buy more credits",
"DOCUMENTS": "Documents",
"RESPONSES": "Responses",
"UPGRADE": "Captain is not available on the free plan, upgrade now to get access to assistants, copilot and more."
},
"CHAT_WITH_US": {
"TITLE": "تحتاج مساعدة؟",
"DESCRIPTION": "هل تواجه أي مشاكل في الفواتير؟ نحن هنا للمساعدة.",
@@ -20,7 +20,7 @@
},
"EMAIL": {
"LABEL": "البريد الإلكتروني للعمل",
"PLACEHOLDER": "أدخل عنوان بريدك الإلكتروني للعمل. مثال: bruce{'@'}wayne{'.'}enterprises",
"PLACEHOLDER": "Enter your work email address. E.g., bruce{'@'}wayne{'.'}enterprises",
"ERROR": "Please enter a valid work email address"
},
"PASSWORD": {
@@ -41,4 +41,4 @@
"SUBMIT": "إرسال",
"HAVE_AN_ACCOUNT": "هل لديك حساب مسبق؟"
}
}
}
@@ -99,6 +99,9 @@
},
"fallback": {
"CONTENT": "has shared a url"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -54,6 +54,7 @@
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
"NO_RESPONSE": "No response",
"RESPONSE": "Response",
"RATING_TITLE": "Rating",
"FEEDBACK_TITLE": "Feedback",
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
@@ -352,5 +353,11 @@
"ONE": "{user} is typing",
"TWO": "{user} and {secondUser} are typing",
"MULTIPLE": "{user} and {count} others are typing"
},
"COPILOT": {
"TRY_THESE_PROMPTS": "Try these prompts"
},
"GALLERY_VIEW": {
"ERROR_DOWNLOADING": "Unable to download attachment. Please try again"
}
}
@@ -0,0 +1,77 @@
import advancedFilters from './advancedFilters.json';
import agentBots from './agentBots.json';
import agentMgmt from './agentMgmt.json';
import attributesMgmt from './attributesMgmt.json';
import auditLogs from './auditLogs.json';
import automation from './automation.json';
import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import components from './components.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmt from './csatMgmt.json';
import customRole from './customRole.json';
import datePicker from './datePicker.json';
import emoji from './emoji.json';
import general from './general.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inbox from './inbox.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
import labelsMgmt from './labelsMgmt.json';
import login from './login.json';
import macros from './macros.json';
import report from './report.json';
import resetPassword from './resetPassword.json';
import search from './search.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
export default {
...advancedFilters,
...agentBots,
...agentMgmt,
...attributesMgmt,
...auditLogs,
...automation,
...bulkActions,
...campaign,
...cannedMgmt,
...chatlist,
...components,
...contact,
...contactFilters,
...conversation,
...csatMgmt,
...customRole,
...datePicker,
...emoji,
...general,
...generalSettings,
...helpCenter,
...inbox,
...inboxMgmt,
...integrationApps,
...integrations,
...labelsMgmt,
...login,
...macros,
...report,
...resetPassword,
...search,
...setNewPassword,
...settings,
...signup,
...sla,
...teamsSettings,
...whatsappTemplates,
};
@@ -309,7 +309,217 @@
"SEND_MESSAGE": "Send message...",
"LOADER": "Captain is thinking",
"YOU": "You",
"USE": "Use this"
"USE": "Use this",
"RESET": "Reset"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
"AVAILABLE_ON": "Captain is not available on the free plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"UPGRADE_NOW": "Upgrade now",
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
},
"BANNER": {
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
},
"FORM": {
"CANCEL": "Cancel",
"CREATE": "Create",
"EDIT": "Update"
},
"ASSISTANTS": {
"HEADER": "Assistants",
"ADD_NEW": "Create a new assistant",
"DELETE": {
"TITLE": "Are you sure to delete the assistant?",
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
},
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
"CREATE": {
"TITLE": "Create an assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully created",
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
},
"FORM": {
"NAME": {
"LABEL": "Assistant Name",
"PLACEHOLDER": "Enter a name for the assistant",
"ERROR": "Please provide a name for the assistant"
},
"DESCRIPTION": {
"LABEL": "Assistant Description",
"PLACEHOLDER": "Describe how and where this assistant will be used",
"ERROR": "A description is required"
},
"PRODUCT_NAME": {
"LABEL": "Product Name",
"PLACEHOLDER": "Enter the name of the product this assistant is designed for",
"ERROR": "The product name is required"
},
"FEATURES": {
"TITLE": "Features",
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions."
}
},
"EDIT": {
"TITLE": "Update the assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the assistant, please try again."
},
"OPTIONS": {
"EDIT_ASSISTANT": "Edit Assistant",
"DELETE_ASSISTANT": "Delete Assistant",
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
},
"EMPTY_STATE": {
"TITLE": "No assistants available",
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations."
}
},
"DOCUMENTS": {
"HEADER": "Documents",
"ADD_NEW": "Create a new document",
"RELATED_RESPONSES": {
"TITLE": "Related FAQs",
"DESCRIPTION": "These FAQs are generated directly from the document."
},
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": {
"TITLE": "Add a document",
"SUCCESS_MESSAGE": "The document has been successfully created",
"ERROR_MESSAGE": "There was an error creating the document, please try again."
},
"FORM": {
"URL": {
"LABEL": "URL",
"PLACEHOLDER": "Enter the URL of the document",
"ERROR": "Please provide a valid URL for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
}
},
"DELETE": {
"TITLE": "Are you sure to delete the document?",
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The document has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "View Related Responses",
"DELETE_DOCUMENT": "Delete Document"
},
"EMPTY_STATE": {
"TITLE": "No documents available",
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant."
}
},
"RESPONSES": {
"HEADER": "FAQs",
"ADD_NEW": "Create new FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversation #{id}"
},
"DELETE": {
"TITLE": "Are you sure to delete the FAQ?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "FAQ deleted successfully",
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
},
"FILTER": {
"ASSISTANT": "Assistant: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "All"
},
"STATUS": {
"TITLE": "Status",
"PENDING": "Pending",
"APPROVED": "Approved",
"ALL": "All"
},
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
"CREATE": {
"TITLE": "Add an FAQ",
"SUCCESS_MESSAGE": "The response has been added successfully.",
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
},
"FORM": {
"QUESTION": {
"LABEL": "Question",
"PLACEHOLDER": "Enter the question here",
"ERROR": "Please provide a valid question."
},
"ANSWER": {
"LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
}
},
"EDIT": {
"TITLE": "Update the FAQ",
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
},
"OPTIONS": {
"APPROVE": "Mark as approved",
"EDIT_RESPONSE": "Edit FAQ",
"DELETE_RESPONSE": "Delete FAQ"
},
"EMPTY_STATE": {
"TITLE": "No FAQs Found",
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
}
},
"INBOXES": {
"HEADER": "Connected Inboxes",
"ADD_NEW": "Connect a new inbox",
"OPTIONS": {
"DISCONNECT": "Disconnect"
},
"DELETE": {
"TITLE": "Are you sure to disconnect the inbox?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
},
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
"CREATE": {
"TITLE": "Connect an Inbox",
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
},
"FORM": {
"INBOX": {
"LABEL": "Inbox",
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
"ERROR": "An inbox selection is required."
}
},
"EMPTY_STATE": {
"TITLE": "No Connected Inboxes",
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
}
}
}
}
@@ -124,6 +124,7 @@
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agents name to learn more.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
@@ -258,6 +259,7 @@
},
"INBOX_REPORTS": {
"HEADER": "Inbox Overview",
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
@@ -325,6 +327,7 @@
},
"TEAM_REPORTS": {
"HEADER": "Team Overview",
"DESCRIPTION": "Get a snapshot of your teams performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_TEAM_REPORTS": "Download team reports",
@@ -538,5 +541,15 @@
},
"VIEW_DETAILS": "View Details"
}
},
"SUMMARY_REPORTS": {
"INBOX": "Inbox",
"AGENT": "Agent",
"TEAM": "Team",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
"RESOLUTION_COUNT": "Resolution Count",
"CONVERSATIONS": "No. of conversations"
}
}
@@ -11,7 +11,10 @@
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages"
},
"VIEW_MORE": "View more",
"LOAD_MORE": "Load more",
"SEARCHING_DATA": "Searching",
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
@@ -21,6 +24,7 @@
"READ_MORE": "Read more",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email"
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
}
}
@@ -194,7 +194,7 @@
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "Change",
"CHANGE_ACCOUNTS": "Switch account",
"SWITCH_WORKSPACE": "Switch workspace",
"SWITCH_ACCOUNT": "Switch account",
"CONTACT_SUPPORT": "Contact support",
"SELECTOR_SUBTITLE": "Select an account from the following list",
"PROFILE_SETTINGS": "Profile settings",
@@ -263,6 +263,9 @@
"SETTINGS": "Settings",
"CONTACTS": "Contacts",
"CAPTAIN": "Captain",
"CAPTAIN_ASSISTANTS": "Assistants",
"CAPTAIN_DOCUMENTS": "Documents",
"CAPTAIN_RESPONSES": "FAQs",
"HOME": "Home",
"AGENTS": "Agents",
"AGENT_BOTS": "Bots",
@@ -324,15 +327,27 @@
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
"CURRENT_PLAN": {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses"
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses",
"SEAT_COUNT": "Number of seats",
"RENEWS_ON": "Renews on"
},
"VIEW_PRICING": "View Pricing",
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
},
"CAPTAIN": {
"TITLE": "Captain",
"DESCRIPTION": "Manage usage and credits for Captain AI.",
"BUTTON_TXT": "Buy more credits",
"DOCUMENTS": "Documents",
"RESPONSES": "Responses",
"UPGRADE": "Captain is not available on the free plan, upgrade now to get access to assistants, copilot and more."
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
@@ -99,6 +99,9 @@
},
"fallback": {
"CONTENT": "сподели линк"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -54,6 +54,7 @@
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
"NO_RESPONSE": "No response",
"RESPONSE": "Response",
"RATING_TITLE": "Rating",
"FEEDBACK_TITLE": "Feedback",
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
@@ -352,5 +353,11 @@
"ONE": "{user} is typing",
"TWO": "{user} and {secondUser} are typing",
"MULTIPLE": "{user} and {count} others are typing"
},
"COPILOT": {
"TRY_THESE_PROMPTS": "Try these prompts"
},
"GALLERY_VIEW": {
"ERROR_DOWNLOADING": "Unable to download attachment. Please try again"
}
}
@@ -8,13 +8,18 @@ import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import components from './components.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmt from './csatMgmt.json';
import customRole from './customRole.json';
import datePicker from './datePicker.json';
import emoji from './emoji.json';
import general from './general.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inbox from './inbox.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
@@ -27,6 +32,7 @@ import search from './search.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
@@ -41,13 +47,18 @@ export default {
...campaign,
...cannedMgmt,
...chatlist,
...components,
...contact,
...contactFilters,
...conversation,
...csatMgmt,
...customRole,
...datePicker,
...emoji,
...general,
...generalSettings,
...helpCenter,
...inbox,
...inboxMgmt,
...integrationApps,
...integrations,
@@ -60,6 +71,7 @@ export default {
...setNewPassword,
...settings,
...signup,
...sla,
...teamsSettings,
...whatsappTemplates,
};
@@ -309,7 +309,217 @@
"SEND_MESSAGE": "Изпрати съобщение...",
"LOADER": "Captain is thinking",
"YOU": "You",
"USE": "Use this"
"USE": "Use this",
"RESET": "Reset"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
"AVAILABLE_ON": "Captain is not available on the free plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"UPGRADE_NOW": "Upgrade now",
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
},
"BANNER": {
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
},
"FORM": {
"CANCEL": "Отмени",
"CREATE": "Създаване",
"EDIT": "Обновяване"
},
"ASSISTANTS": {
"HEADER": "Assistants",
"ADD_NEW": "Create a new assistant",
"DELETE": {
"TITLE": "Are you sure to delete the assistant?",
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
},
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
"CREATE": {
"TITLE": "Create an assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully created",
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
},
"FORM": {
"NAME": {
"LABEL": "Assistant Name",
"PLACEHOLDER": "Enter a name for the assistant",
"ERROR": "Please provide a name for the assistant"
},
"DESCRIPTION": {
"LABEL": "Assistant Description",
"PLACEHOLDER": "Describe how and where this assistant will be used",
"ERROR": "A description is required"
},
"PRODUCT_NAME": {
"LABEL": "Product Name",
"PLACEHOLDER": "Enter the name of the product this assistant is designed for",
"ERROR": "The product name is required"
},
"FEATURES": {
"TITLE": "Features",
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions."
}
},
"EDIT": {
"TITLE": "Update the assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the assistant, please try again."
},
"OPTIONS": {
"EDIT_ASSISTANT": "Edit Assistant",
"DELETE_ASSISTANT": "Delete Assistant",
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
},
"EMPTY_STATE": {
"TITLE": "No assistants available",
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations."
}
},
"DOCUMENTS": {
"HEADER": "Documents",
"ADD_NEW": "Create a new document",
"RELATED_RESPONSES": {
"TITLE": "Related FAQs",
"DESCRIPTION": "These FAQs are generated directly from the document."
},
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": {
"TITLE": "Add a document",
"SUCCESS_MESSAGE": "The document has been successfully created",
"ERROR_MESSAGE": "There was an error creating the document, please try again."
},
"FORM": {
"URL": {
"LABEL": "URL",
"PLACEHOLDER": "Enter the URL of the document",
"ERROR": "Please provide a valid URL for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
}
},
"DELETE": {
"TITLE": "Are you sure to delete the document?",
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The document has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "View Related Responses",
"DELETE_DOCUMENT": "Delete Document"
},
"EMPTY_STATE": {
"TITLE": "No documents available",
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant."
}
},
"RESPONSES": {
"HEADER": "FAQs",
"ADD_NEW": "Create new FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversation #{id}"
},
"DELETE": {
"TITLE": "Are you sure to delete the FAQ?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "FAQ deleted successfully",
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
},
"FILTER": {
"ASSISTANT": "Assistant: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "Всички"
},
"STATUS": {
"TITLE": "Статус",
"PENDING": "Предстоящ",
"APPROVED": "Approved",
"ALL": "Всички"
},
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
"CREATE": {
"TITLE": "Add an FAQ",
"SUCCESS_MESSAGE": "The response has been added successfully.",
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
},
"FORM": {
"QUESTION": {
"LABEL": "Question",
"PLACEHOLDER": "Enter the question here",
"ERROR": "Please provide a valid question."
},
"ANSWER": {
"LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
}
},
"EDIT": {
"TITLE": "Update the FAQ",
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
},
"OPTIONS": {
"APPROVE": "Mark as approved",
"EDIT_RESPONSE": "Edit FAQ",
"DELETE_RESPONSE": "Delete FAQ"
},
"EMPTY_STATE": {
"TITLE": "No FAQs Found",
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
}
},
"INBOXES": {
"HEADER": "Connected Inboxes",
"ADD_NEW": "Connect a new inbox",
"OPTIONS": {
"DISCONNECT": "Прекъсване"
},
"DELETE": {
"TITLE": "Are you sure to disconnect the inbox?",
"DESCRIPTION": "",
"CONFIRM": "Yes, delete",
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
},
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
"CREATE": {
"TITLE": "Connect an Inbox",
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
},
"FORM": {
"INBOX": {
"LABEL": "Входяща кутия",
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
"ERROR": "An inbox selection is required."
}
},
"EMPTY_STATE": {
"TITLE": "No Connected Inboxes",
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
}
}
}
}
@@ -3,7 +3,7 @@
"TITLE": "Login to Chatwoot",
"EMAIL": {
"LABEL": "Email",
"PLACEHOLDER": "Email eg: someone{'@'}example.com",
"PLACEHOLDER": "example{'@'}companyname.com",
"ERROR": "Please enter a valid email address"
},
"PASSWORD": {
@@ -24,4 +24,4 @@
"CREATE_NEW_ACCOUNT": "Create new account",
"SUBMIT": "Login"
}
}
}
@@ -124,6 +124,7 @@
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agents name to learn more.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
@@ -258,6 +259,7 @@
},
"INBOX_REPORTS": {
"HEADER": "Inbox Overview",
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
@@ -325,6 +327,7 @@
},
"TEAM_REPORTS": {
"HEADER": "Team Overview",
"DESCRIPTION": "Get a snapshot of your teams performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_TEAM_REPORTS": "Download team reports",
@@ -538,5 +541,15 @@
},
"VIEW_DETAILS": "View Details"
}
},
"SUMMARY_REPORTS": {
"INBOX": "Входяща кутия",
"AGENT": "Агент",
"TEAM": "Team",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
"RESOLUTION_COUNT": "Resolution Count",
"CONVERSATIONS": "No. of conversations"
}
}
}
@@ -11,7 +11,10 @@
"CONVERSATIONS": "Разговори",
"MESSAGES": "Messages"
},
"VIEW_MORE": "View more",
"LOAD_MORE": "Load more",
"SEARCHING_DATA": "Searching",
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
@@ -21,6 +24,7 @@
"READ_MORE": "Read more",
"WROTE": "wrote:",
"FROM": "от",
"EMAIL": "имейл"
"EMAIL": "имейл",
"EMAIL_SUBJECT": "тема"
}
}
@@ -194,7 +194,7 @@
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "Change",
"CHANGE_ACCOUNTS": "Switch account",
"SWITCH_WORKSPACE": "Switch workspace",
"SWITCH_ACCOUNT": "Switch account",
"CONTACT_SUPPORT": "Contact support",
"SELECTOR_SUBTITLE": "Select an account from the following list",
"PROFILE_SETTINGS": "Profile settings",
@@ -263,6 +263,9 @@
"SETTINGS": "Settings",
"CONTACTS": "Контакти",
"CAPTAIN": "Captain",
"CAPTAIN_ASSISTANTS": "Assistants",
"CAPTAIN_DOCUMENTS": "Documents",
"CAPTAIN_RESPONSES": "FAQs",
"HOME": "Home",
"AGENTS": "Агенти",
"AGENT_BOTS": "Bots",
@@ -324,15 +327,27 @@
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
"CURRENT_PLAN": {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses"
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses",
"SEAT_COUNT": "Number of seats",
"RENEWS_ON": "Renews on"
},
"VIEW_PRICING": "View Pricing",
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
},
"CAPTAIN": {
"TITLE": "Captain",
"DESCRIPTION": "Manage usage and credits for Captain AI.",
"BUTTON_TXT": "Buy more credits",
"DOCUMENTS": "Documents",
"RESPONSES": "Responses",
"UPGRADE": "Captain is not available on the free plan, upgrade now to get access to assistants, copilot and more."
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
@@ -20,7 +20,7 @@
},
"EMAIL": {
"LABEL": "Work email",
"PLACEHOLDER": "Enter your work email address. eg: bruce{'@'}wayne{'.'}enterprises",
"PLACEHOLDER": "Enter your work email address. E.g., bruce{'@'}wayne{'.'}enterprises",
"ERROR": "Please enter a valid work email address"
},
"PASSWORD": {
@@ -41,4 +41,4 @@
"SUBMIT": "Create account",
"HAVE_AN_ACCOUNT": "Already have an account?"
}
}
}
@@ -1,74 +1,74 @@
{
"AUDIT_LOGS": {
"HEADER": "Registres d'auditoria",
"HEADER_BTN_TXT": "Afegeix registres d'auditoria",
"LOADING": "S'estan recollint registres d'auditoria",
"DESCRIPTION": "Audit Logs maintain a record of activities in your account, allowing you to track and audit your account, team, or services.",
"LEARN_MORE": "Learn more about audit logs",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
"SIDEBAR_TXT": "<p><b>Registres d'auditoria</b> </p><p> Els registres d'auditoria són pistes d'esdeveniments i accions en un sistema de Chatwoot. </p>",
"LIST": {
"404": "No hi ha registres d'auditoria disponibles en aquest compte.",
"TITLE": "Gestiona els registres d'auditoria",
"DESC": "Els registres d'auditoria són pistes d'esdeveniments i accions en un sistema Chatwoot.",
"TABLE_HEADER": {
"ACTIVITY": "User",
"TIME": "Temps",
"IP_ADDRESS": "Adreça IP"
}
},
"API": {
"SUCCESS_MESSAGE": "Els registres d'auditoria s'han recuperat correctament",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
},
"DEFAULT_USER": "Sistema",
"AUTOMATION_RULE": {
"ADD": "{agentName} ha creat una nova regla d'automatització (#{id})",
"EDIT": "{agentName} ha actualitzat una regla d'automatització (#{id})",
"DELETE": "{agentName} ha suprimit una regla d'automatització (#{id})"
},
"ACCOUNT_USER": {
"ADD": "{agentName} ha convidat {invitee} al compte com a {role}",
"EDIT": {
"SELF": "{agentName} ha canviat els seus {attributes} a {values}",
"OTHER": "{agentName} ha canviat {attributes} de {user} a {values}",
"DELETED": "{agentName} changed {attributes} of a deleted user to {values}"
}
},
"INBOX": {
"ADD": "{agentName} ha creat una safata d'entrada nova (#{id})",
"EDIT": "{agentName} ha actualitzat una safata d'entrada (#{id})",
"DELETE": "{agentName} ha suprimit una safata d'entrada (#{id})"
},
"WEBHOOK": {
"ADD": "{agentName} ha creat un nou webhook (#{id})",
"EDIT": "{agentName} ha actualitzat un webhook (#{id})",
"DELETE": "{agentName} ha suprimit un webhook (#{id})"
},
"USER_ACTION": {
"SIGN_IN": "{agentName} ha iniciat la sessió",
"SIGN_OUT": "{agentName} ha tancat la sessió"
},
"TEAM": {
"ADD": "{agentName} ha creat un equip nou (#{id})",
"EDIT": "{agentName} ha actualitzat un equip (#{id})",
"DELETE": "{agentName} ha suprimit un equip (#{id})"
},
"MACRO": {
"ADD": "{agentName} ha creat una macro nova (#{id})",
"EDIT": "{agentName} ha actualitzat una macro (#{id})",
"DELETE": "{agentName} ha suprimit una macro (#{id})"
},
"INBOX_MEMBER": {
"ADD": "{agentName} ha afegit {user} a la safata d'entrada(#{inbox_id})",
"REMOVE": "{agentName} ha eliminat {user} de la safata d'entrada(#{inbox_id})"
},
"TEAM_MEMBER": {
"ADD": "{agentName} ha afegit {user} a l'equip (#{team_id})",
"REMOVE": "{agentName} ha eliminat {user} de l'equip (#{team_id})"
},
"ACCOUNT": {
"EDIT": "{agentName} ha actualitzat la configuració del compte (#{id})"
}
"AUDIT_LOGS": {
"HEADER": "Registres d'auditoria",
"HEADER_BTN_TXT": "Afegeix registres d'auditoria",
"LOADING": "S'estan recollint registres d'auditoria",
"DESCRIPTION": "Audit Logs maintain a record of activities in your account, allowing you to track and audit your account, team, or services.",
"LEARN_MORE": "Learn more about audit logs",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
"SIDEBAR_TXT": "<p><b>Registres d'auditoria</b> </p><p> Els registres d'auditoria són pistes d'esdeveniments i accions en un sistema de Chatwoot. </p>",
"LIST": {
"404": "No hi ha registres d'auditoria disponibles en aquest compte.",
"TITLE": "Gestiona els registres d'auditoria",
"DESC": "Els registres d'auditoria són pistes d'esdeveniments i accions en un sistema Chatwoot.",
"TABLE_HEADER": {
"ACTIVITY": "User",
"TIME": "Temps",
"IP_ADDRESS": "Adreça IP"
}
}
},
"API": {
"SUCCESS_MESSAGE": "Els registres d'auditoria s'han recuperat correctament",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
},
"DEFAULT_USER": "Sistema",
"AUTOMATION_RULE": {
"ADD": "{agentName} created a new automation rule (#{id})",
"EDIT": "{agentName} updated an automation rule (#{id})",
"DELETE": "{agentName} deleted an automation rule (#{id})"
},
"ACCOUNT_USER": {
"ADD": "{agentName} ha convidat {invitee} al compte com a {role}",
"EDIT": {
"SELF": "{agentName} ha canviat els seus {attributes} a {values}",
"OTHER": "{agentName} ha canviat {attributes} de {user} a {values}",
"DELETED": "{agentName} changed {attributes} of a deleted user to {values}"
}
},
"INBOX": {
"ADD": "{agentName} created a new inbox (#{id})",
"EDIT": "{agentName} updated an inbox (#{id})",
"DELETE": "{agentName} deleted an inbox (#{id})"
},
"WEBHOOK": {
"ADD": "{agentName} created a new webhook (#{id})",
"EDIT": "{agentName} updated a webhook (#{id})",
"DELETE": "{agentName} deleted a webhook (#{id})"
},
"USER_ACTION": {
"SIGN_IN": "{agentName} ha iniciat la sessió",
"SIGN_OUT": "{agentName} ha tancat la sessió"
},
"TEAM": {
"ADD": "{agentName} created a new team (#{id})",
"EDIT": "{agentName} updated a team (#{id})",
"DELETE": "{agentName} deleted a team (#{id})"
},
"MACRO": {
"ADD": "{agentName} created a new macro (#{id})",
"EDIT": "{agentName} updated a macro (#{id})",
"DELETE": "{agentName} deleted a macro (#{id})"
},
"INBOX_MEMBER": {
"ADD": "{agentName} added {user} to the inbox(#{inbox_id})",
"REMOVE": "{agentName} removed {user} from the inbox(#{inbox_id})"
},
"TEAM_MEMBER": {
"ADD": "{agentName} added {user} to the team(#{team_id})",
"REMOVE": "{agentName} removed {user} from the team(#{team_id})"
},
"ACCOUNT": {
"EDIT": "{agentName} updated the account configuration (#{id})"
}
}
}
@@ -99,6 +99,9 @@
},
"fallback": {
"CONTENT": "ha compartit una URL"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -54,6 +54,7 @@
"SUCCESS_DELETE_MESSAGE": "El missatge s'ha suprimit correctament",
"FAIL_DELETE_MESSSAGE": "No s'ha pogut suprimir el missatge! Torna-ho a provar",
"NO_RESPONSE": "Sense resposta",
"RESPONSE": "Response",
"RATING_TITLE": "Valoració",
"FEEDBACK_TITLE": "Comentaris",
"REPLY_MESSAGE_NOT_FOUND": "Missatge no disponible",
@@ -108,7 +109,7 @@
"SELECT_PLACEHOLDER": "Ningú",
"INPUT_PLACEHOLDER": "Selecciona la prioritat",
"NO_RESULTS": "No s'ha trobat agents",
"SUCCESSFUL": "S'ha canviat la prioritat de l'identificador de conversa {conversationId} a {priority}",
"SUCCESSFUL": "S'ha canviat la prioritat de l'ID de conversa {conversationId} a {priority}",
"FAILED": "No s'ha pogut canviar la prioritat. Si us plau, torna-ho a provar."
}
},
@@ -133,7 +134,7 @@
"FAILED": "No s'ha pogut assignar l'agent. Torna-ho a provar."
},
"LABEL_ASSIGNMENT": {
"SUCCESFUL": "S'ha assignat l'etiqueta #{labelName} a l'id de conversa {conversationId}",
"SUCCESFUL": "Assigned label #{labelName} to conversation id {conversationId}",
"FAILED": "No s'ha pogut assignar l'etiqueta. Torna-ho a provar."
},
"TEAM_ASSIGNMENT": {
@@ -352,5 +353,11 @@
"ONE": "{user} is typing",
"TWO": "{user} and {secondUser} are typing",
"MULTIPLE": "{user} and {count} others are typing"
},
"COPILOT": {
"TRY_THESE_PROMPTS": "Try these prompts"
},
"GALLERY_VIEW": {
"ERROR_DOWNLOADING": "Unable to download attachment. Please try again"
}
}
}
@@ -8,13 +8,18 @@ import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import components from './components.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmt from './csatMgmt.json';
import customRole from './customRole.json';
import datePicker from './datePicker.json';
import emoji from './emoji.json';
import general from './general.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inbox from './inbox.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
@@ -27,6 +32,7 @@ import search from './search.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
@@ -41,13 +47,18 @@ export default {
...campaign,
...cannedMgmt,
...chatlist,
...components,
...contact,
...contactFilters,
...conversation,
...csatMgmt,
...customRole,
...datePicker,
...emoji,
...general,
...generalSettings,
...helpCenter,
...inbox,
...inboxMgmt,
...integrationApps,
...integrations,
@@ -60,6 +71,7 @@ export default {
...setNewPassword,
...settings,
...signup,
...sla,
...teamsSettings,
...whatsappTemplates,
};
@@ -56,7 +56,7 @@
"BUTTON_TEXT": "Desconnecta"
},
"SIDEBAR_DESCRIPTION": {
"DIALOGFLOW": "Dialogflow és una plataforma de comprensió del llenguatge natural que facilita el disseny i la integració d'una interfície d'usuari conversacional a la teva aplicació mòbil, aplicació web, dispositiu, bot, sistema interactiu de resposta de veu, etc. <br /> <br /> La integració de Dialogflow amb {installationName} us permet configurar un bot de Dialogflow amb les teves safates d'entrada, la qual cosa permet al bot gestionar les consultes inicialment i lliurar-les a un agent quan sigui necessari. Dialogflow es pot utilitzar per qualificar els clients potencials, reduir la càrrega de treball dels agents proporcionant preguntes freqüents, etc. <br /> <br /> Per afegir Dialogflow, has de crear un compte de servei a la consola del vostre projecte de Google i compartir les credencials. Consulta els documents de Dialogflow per obtenir més informació."
"DIALOGFLOW": "Dialogflow is a natural language understanding platform that makes it easy to design and integrate a conversational user interface into your mobile app, web application, device, bot, interactive voice response system, and so on. <br /> <br /> Dialogflow integration with {installationName} allows you to configure a Dialogflow bot with your inboxes which lets the bot handle the queries initially and hand them over to an agent when needed. Dialogflow can be used to qualifying the leads, reduce the workload of agents by providing frequently asked questions etc. <br /> <br /> To add Dialogflow, you need to create a Service Account in your Google project console and share the credentials. Please refer to the Dialogflow docs for more information."
}
}
}
}
@@ -90,7 +90,7 @@
},
"HELP_TEXT": {
"TITLE": "Com utilitzar la integració de Slack?",
"BODY": "Amb aquesta integració, totes les teves converses entrants se sincronitzaran amb el canal ***{selectedChannelName}*** del teu espai de treball de Slack. Podeu gestionar totes les converses dels vostres clients directament des del canal i no perdreu cap missatge.\n\nAquestes són les principals característiques de la integració:\n\n**Respondre a converses des de Slack:** Per respondre a una conversa al ***{selectedChannelName}*** canal de Slack, només has d'escriure el teu missatge i enviar-lo com a fil. Això crearà una resposta al client mitjançant Chatwoot. Així de senzill!\n\n **Crear notes privades:** si vols crear notes privades en lloc de respostes, comença el teu missatge amb ***`nota:`***. Això garanteix que el vostre missatge es mantingui privat i no serà visible per al client.\n\n**Associar un perfil d'agent:** si la persona que va respondre a Slack té un perfil d'agent a Chatwoot amb el mateix correu electrònic, les respostes s'associaran amb aquest perfil d'agent automàticament. Això vol dir que podeu fer un seguiment fàcilment qui va dir què i quan. D'altra banda, quan el contestador no té un perfil d'agent associat, les respostes apareixeran des del perfil del bot al client.",
"BODY": "With this integration, all of your incoming conversations will be synced to the ***{selectedChannelName}*** channel in your Slack workspace. You can manage all your customer conversations right within the channel and never miss a message.\n\nHere are the main features of the integration:\n\n**Respond to conversations from within Slack:** To respond to a conversation in the ***{selectedChannelName}*** Slack channel, simply type out your message and send it as a thread. This will create a response back to the customer through Chatwoot. It's that simple!\n\n **Create private notes:** If you want to create private notes instead of replies, start your message with ***`note:`***. This ensures that your message is kept private and won't be visible to the customer.\n\n**Associate an agent profile:** If the person who replied on Slack has an agent profile in Chatwoot under the same email, the replies will be associated with that agent profile automatically. This means you can easily track who said what and when. On the other hand, when the replier doesn't have an associated agent profile, the replies will appear from the bot profile to the customer.",
"SELECTED": "seleccionat"
},
"SELECT_CHANNEL": {
@@ -114,7 +114,7 @@
},
"OPEN_AI": {
"AI_ASSIST": "Assistència IA",
"WITH_AI": " {option} amb IA ",
"WITH_AI": " {option} with AI ",
"OPTIONS": {
"REPLY_SUGGESTION": "Suggeriment de resposta",
"SUMMARIZE": "Resumir",
@@ -235,7 +235,7 @@
"ERROR": "S'ha produït un error en obtenir les issues en Linear, torna-ho a provar",
"LINK_SUCCESS": "S'ha enllaçat la issue correctament",
"LINK_ERROR": "S'ha produït un error en enllaçar la issue, torna-ho a provar",
"LINK_TITLE": "Conversa (#{conversationId}) amb {name}"
"LINK_TITLE": "Conversation (#{conversationId}) with {name}"
},
"ADD_OR_LINK": {
"TITLE": "Crear/enllaçar una issue en Linear",
@@ -294,7 +294,7 @@
"PRIORITY": "Prioritat",
"ASSIGNEE": "Cessionari",
"LABELS": "Etiquetes",
"CREATED_AT": "Creat a {createdAt}"
"CREATED_AT": "Created at {createdAt}"
},
"UNLINK": {
"TITLE": "Desenllaça",
@@ -309,7 +309,217 @@
"SEND_MESSAGE": "Envia missatge...",
"LOADER": "Captain is thinking",
"YOU": "Tu",
"USE": "Use this"
"USE": "Use this",
"RESET": "Reset"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
"AVAILABLE_ON": "Captain is not available on the free plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"UPGRADE_NOW": "Actualitza ara",
"CANCEL_ANYTIME": "Pots canviar o cancel·lar el teu pla en qualsevol moment"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
"ASK_ADMIN": "Posa't en contacte amb el vostre administrador per obtenir l'actualització."
},
"BANNER": {
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
},
"FORM": {
"CANCEL": "Cancel·la",
"CREATE": "Crear",
"EDIT": "Actualitza"
},
"ASSISTANTS": {
"HEADER": "Assistants",
"ADD_NEW": "Create a new assistant",
"DELETE": {
"TITLE": "Are you sure to delete the assistant?",
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
"CONFIRM": "Sí, esborra",
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
},
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
"CREATE": {
"TITLE": "Create an assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully created",
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
},
"FORM": {
"NAME": {
"LABEL": "Assistant Name",
"PLACEHOLDER": "Enter a name for the assistant",
"ERROR": "Please provide a name for the assistant"
},
"DESCRIPTION": {
"LABEL": "Assistant Description",
"PLACEHOLDER": "Describe how and where this assistant will be used",
"ERROR": "A description is required"
},
"PRODUCT_NAME": {
"LABEL": "Product Name",
"PLACEHOLDER": "Enter the name of the product this assistant is designed for",
"ERROR": "The product name is required"
},
"FEATURES": {
"TITLE": "Característiques",
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions."
}
},
"EDIT": {
"TITLE": "Update the assistant",
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the assistant, please try again."
},
"OPTIONS": {
"EDIT_ASSISTANT": "Edit Assistant",
"DELETE_ASSISTANT": "Delete Assistant",
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
},
"EMPTY_STATE": {
"TITLE": "No assistants available",
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations."
}
},
"DOCUMENTS": {
"HEADER": "Documents",
"ADD_NEW": "Create a new document",
"RELATED_RESPONSES": {
"TITLE": "Related FAQs",
"DESCRIPTION": "These FAQs are generated directly from the document."
},
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": {
"TITLE": "Add a document",
"SUCCESS_MESSAGE": "The document has been successfully created",
"ERROR_MESSAGE": "There was an error creating the document, please try again."
},
"FORM": {
"URL": {
"LABEL": "URL",
"PLACEHOLDER": "Enter the URL of the document",
"ERROR": "Please provide a valid URL for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
}
},
"DELETE": {
"TITLE": "Are you sure to delete the document?",
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
"CONFIRM": "Sí, esborra",
"SUCCESS_MESSAGE": "The document has been successfully deleted",
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "View Related Responses",
"DELETE_DOCUMENT": "Delete Document"
},
"EMPTY_STATE": {
"TITLE": "No documents available",
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant."
}
},
"RESPONSES": {
"HEADER": "FAQs",
"ADD_NEW": "Create new FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversation #{id}"
},
"DELETE": {
"TITLE": "Are you sure to delete the FAQ?",
"DESCRIPTION": "",
"CONFIRM": "Sí, esborra",
"SUCCESS_MESSAGE": "FAQ deleted successfully",
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
},
"FILTER": {
"ASSISTANT": "Assistant: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "Totes"
},
"STATUS": {
"TITLE": "Estat",
"PENDING": "Pendent",
"APPROVED": "Approved",
"ALL": "Totes"
},
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
"CREATE": {
"TITLE": "Add an FAQ",
"SUCCESS_MESSAGE": "The response has been added successfully.",
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
},
"FORM": {
"QUESTION": {
"LABEL": "Question",
"PLACEHOLDER": "Enter the question here",
"ERROR": "Please provide a valid question."
},
"ANSWER": {
"LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
}
},
"EDIT": {
"TITLE": "Update the FAQ",
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
},
"OPTIONS": {
"APPROVE": "Mark as approved",
"EDIT_RESPONSE": "Edit FAQ",
"DELETE_RESPONSE": "Delete FAQ"
},
"EMPTY_STATE": {
"TITLE": "No FAQs Found",
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
}
},
"INBOXES": {
"HEADER": "Connected Inboxes",
"ADD_NEW": "Connect a new inbox",
"OPTIONS": {
"DISCONNECT": "Desconnecta"
},
"DELETE": {
"TITLE": "Are you sure to disconnect the inbox?",
"DESCRIPTION": "",
"CONFIRM": "Sí, esborra",
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
},
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
"CREATE": {
"TITLE": "Connect an Inbox",
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
},
"FORM": {
"INBOX": {
"LABEL": "Safata d'entrada",
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
"ERROR": "An inbox selection is required."
}
},
"EMPTY_STATE": {
"TITLE": "No Connected Inboxes",
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
}
}
}
}
}
@@ -3,7 +3,7 @@
"TITLE": "Entra a Chatwoot",
"EMAIL": {
"LABEL": "Correu electrònic",
"PLACEHOLDER": "exemple{'@'}nomdelacompanyia.com",
"PLACEHOLDER": "example{'@'}companyname.com",
"ERROR": "Introduïu una adreça de correu electrònic vàlida"
},
"PASSWORD": {
@@ -24,4 +24,4 @@
"CREATE_NEW_ACCOUNT": "Crear un nou compte",
"SUBMIT": "Inicia la sessió"
}
}
}
@@ -124,6 +124,7 @@
},
"AGENT_REPORTS": {
"HEADER": "Visió general dels agents",
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agents name to learn more.",
"LOADING_CHART": "S'estan carregant dades del gràfic...",
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
"DOWNLOAD_AGENT_REPORTS": "Descarregar Informes d'Agent",
@@ -258,6 +259,7 @@
},
"INBOX_REPORTS": {
"HEADER": "Visió general de la safata d'entrada",
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
"LOADING_CHART": "S'estan carregant dades del gràfic...",
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
"DOWNLOAD_INBOX_REPORTS": "Baixa els informes de la safata d'entrada",
@@ -325,6 +327,7 @@
},
"TEAM_REPORTS": {
"HEADER": "Visió general de l'equip",
"DESCRIPTION": "Get a snapshot of your teams performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
"LOADING_CHART": "S'estan carregant dades del gràfic...",
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
"DOWNLOAD_TEAM_REPORTS": "Descarregar Informes d'equip",
@@ -538,5 +541,15 @@
},
"VIEW_DETAILS": "Veure Detalls"
}
},
"SUMMARY_REPORTS": {
"INBOX": "Safata d'entrada",
"AGENT": "Agent",
"TEAM": "Equip",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
"RESOLUTION_COUNT": "Total de resolucions",
"CONVERSATIONS": "Nre. de converses"
}
}
}
@@ -11,7 +11,10 @@
"CONVERSATIONS": "Converses",
"MESSAGES": "Missatges"
},
"VIEW_MORE": "View more",
"LOAD_MORE": "Load more",
"SEARCHING_DATA": "S'està cercant",
"LOADING_DATA": "Carregant",
"EMPTY_STATE": "No s'ha trobat cap {item} per a la consulta '{query}'",
"EMPTY_STATE_FULL": "No s'han trobat resultats per a la consulta '{query}'",
"PLACEHOLDER_KEYBINDING": "/ centrar",
@@ -21,6 +24,7 @@
"READ_MORE": "Llegir més",
"WROTE": "va escriure:",
"FROM": "des de",
"EMAIL": "correu electrònic"
"EMAIL": "correu electrònic",
"EMAIL_SUBJECT": "assumpte"
}
}

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