Compare commits
283
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e75ac2de5 | ||
|
|
91c1061214 | ||
|
|
ec65b43993 | ||
|
|
1a81245987 | ||
|
|
2c3ad73542 | ||
|
|
59b1d59574 | ||
|
|
9de2edd300 | ||
|
|
66751b71dd | ||
|
|
7e415fbee3 | ||
|
|
50a927bac2 | ||
|
|
19ff738211 | ||
|
|
7c080fa9fa | ||
|
|
91e2da5e74 | ||
|
|
21691f2407 | ||
|
|
d495411ef5 | ||
|
|
918e1f25c2 | ||
|
|
10dd0ba647 | ||
|
|
550bea099c | ||
|
|
c7bf93ef8a | ||
|
|
ec7434d000 | ||
|
|
941e7e06cf | ||
|
|
09f46aa912 | ||
|
|
332ab5888c | ||
|
|
a940f4c1d2 | ||
|
|
5b4f92ccd2 | ||
|
|
ad03be4529 | ||
|
|
571e6bd0ec | ||
|
|
528da1d2cf | ||
|
|
4e8d17f017 | ||
|
|
3054a4cb59 | ||
|
|
71837bedf9 | ||
|
|
3c4514c9f7 | ||
|
|
4c7c1cd78a | ||
|
|
985b3f3bfc | ||
|
|
986abde531 | ||
|
|
b57063a8b8 | ||
|
|
022f4f899f | ||
|
|
191b8a64fe | ||
|
|
ea78020f2f | ||
|
|
dc1a41b136 | ||
|
|
28e7a5d228 | ||
|
|
40830046e8 | ||
|
|
d05c953eef | ||
|
|
4a770fdea7 | ||
|
|
239b1e2bf1 | ||
|
|
f963e00731 | ||
|
|
155a5b9947 | ||
|
|
26a75b250d | ||
|
|
20b84ee6a1 | ||
|
|
f1f14312fe | ||
|
|
212d7caab1 | ||
|
|
9cda16d0e8 | ||
|
|
a8acbb7427 | ||
|
|
1176e5eb8a | ||
|
|
996325f35b | ||
|
|
4f8ce7b597 | ||
|
|
f8e631a00c | ||
|
|
54bf33083e | ||
|
|
533f9f7fe4 | ||
|
|
82e5fc413e | ||
|
|
93daaea19b | ||
|
|
e6a49b5800 | ||
|
|
595e6e79f0 | ||
|
|
93d8157a55 | ||
|
|
6bea2cbc4a | ||
|
|
afb7e67795 | ||
|
|
bc813b71dd | ||
|
|
9ac4a1eb30 | ||
|
|
9bb455ec2d | ||
|
|
c410fe333a | ||
|
|
5e807c5875 | ||
|
|
9bed57c7d1 | ||
|
|
5c5381c0a9 | ||
|
|
138630b4ae | ||
|
|
9d0de04f7c | ||
|
|
2c3337b117 | ||
|
|
237358af24 | ||
|
|
35dfff0a5b | ||
|
|
86b2896333 | ||
|
|
e8a27bea4b | ||
|
|
0d465362ac | ||
|
|
e383e9259b | ||
|
|
2c34dca347 | ||
|
|
69b9123963 | ||
|
|
cac9fe1880 | ||
|
|
06ac7c829b | ||
|
|
9985f1c143 | ||
|
|
1ee6a8fe90 | ||
|
|
6eb4fa41ff | ||
|
|
8b91f0ffac | ||
|
|
d2aa19579e | ||
|
|
2e79a32db7 | ||
|
|
a86e236d19 | ||
|
|
c755978352 | ||
|
|
23ca6d56f9 | ||
|
|
429ec7194f | ||
|
|
c99d9f9557 | ||
|
|
9ff5978d6e | ||
|
|
cd28f401ba | ||
|
|
879a244f93 | ||
|
|
f64f2138db | ||
|
|
48f2e58e59 | ||
|
|
c715e396f0 | ||
|
|
c8fac08e0b | ||
|
|
7fd220c177 | ||
|
|
b3b76d00ff | ||
|
|
2f2ae88cba | ||
|
|
2b7989f52b | ||
|
|
d7314079c9 | ||
|
|
1017903ee1 | ||
|
|
d25e7fd54e | ||
|
|
7e89e3a34a | ||
|
|
39eaed7766 | ||
|
|
b1a4907ede | ||
|
|
a01d81a7e1 | ||
|
|
117d5301b4 | ||
|
|
aae6081d73 | ||
|
|
d03924b846 | ||
|
|
b333d0c986 | ||
|
|
9f3d155822 | ||
|
|
9203b097d2 | ||
|
|
12cb97db5f | ||
|
|
09971fd613 | ||
|
|
ca9d108741 | ||
|
|
f28533bc47 | ||
|
|
68f82c11a5 | ||
|
|
abc27fa791 | ||
|
|
d93a8d05bc | ||
|
|
373f5f5b64 | ||
|
|
ae7df60a75 | ||
|
|
412d750b6a | ||
|
|
f1a77ba934 | ||
|
|
ffc6364690 | ||
|
|
3a7633b564 | ||
|
|
91d1d44aa9 | ||
|
|
afc97faa8b | ||
|
|
1d0930ef63 | ||
|
|
776ea6d39a | ||
|
|
2c85098698 | ||
|
|
6bd0e074dc | ||
|
|
123fc73394 | ||
|
|
88cef88d80 | ||
|
|
8e79cf72e1 | ||
|
|
bfaca851f1 | ||
|
|
5618b5ebd3 | ||
|
|
4807052c37 | ||
|
|
44b1047b90 | ||
|
|
9c6c19c3e5 | ||
|
|
2764338453 | ||
|
|
4f0fcfcbcd | ||
|
|
fdbaf98c3b | ||
|
|
03bbd048a7 | ||
|
|
d481b9fbcf | ||
|
|
68dedc37ba | ||
|
|
958c0d7946 | ||
|
|
9553329092 | ||
|
|
d01f2063d0 | ||
|
|
590e4e9c1c | ||
|
|
0e903d2365 | ||
|
|
d6ce1ceeeb | ||
|
|
e8f56d0e56 | ||
|
|
b8dae07c0f | ||
|
|
e3f4be97c0 | ||
|
|
1f4d096804 | ||
|
|
1bcc438b21 | ||
|
|
8b3d13a920 | ||
|
|
dcdd58bae5 | ||
|
|
7b9a9831c1 | ||
|
|
7ab7bac6bf | ||
|
|
b988a01df3 | ||
|
|
105f9a27d2 | ||
|
|
132cf802d5 | ||
|
|
f2f33038f2 | ||
|
|
590ce788b9 | ||
|
|
a25179c342 | ||
|
|
aea9470b6a | ||
|
|
6382707146 | ||
|
|
49ef4e54ca | ||
|
|
db0d32f01d | ||
|
|
de1fb6ba4e | ||
|
|
7b8bcaba11 | ||
|
|
3f39b7ad1f | ||
|
|
a3547c5a1f | ||
|
|
0f776a173c | ||
|
|
d320605556 | ||
|
|
ca85d4e1c6 | ||
|
|
b994706265 | ||
|
|
d9f2cd0a6e | ||
|
|
f2a34ad8aa | ||
|
|
65e91f6a6f | ||
|
|
7dd1562b40 | ||
|
|
c70367bbd7 | ||
|
|
5a8eff35b5 | ||
|
|
1936369b12 | ||
|
|
354010a6e1 | ||
|
|
385eab6b96 | ||
|
|
198cd9b28d | ||
|
|
abdf00d2cf | ||
|
|
2c3160cfee | ||
|
|
708bddf4db | ||
|
|
020dcc4dc7 | ||
|
|
271263bcc2 | ||
|
|
c97d6021e0 | ||
|
|
d99997d17d | ||
|
|
9c5d062efc | ||
|
|
87f758ee1f | ||
|
|
d0a1ad746a | ||
|
|
86384bf876 | ||
|
|
55e5fa2205 | ||
|
|
65bad90b69 | ||
|
|
07aaa046c1 | ||
|
|
520bdabefe | ||
|
|
662967b5d3 | ||
|
|
6137a45214 | ||
|
|
5e0ce7793c | ||
|
|
ce3e38df0f | ||
|
|
2b88ecdfc4 | ||
|
|
683f259771 | ||
|
|
51fb3b7e8e | ||
|
|
3dedfee350 | ||
|
|
022383d942 | ||
|
|
59433d9d3c | ||
|
|
85e57c2e94 | ||
|
|
b081fe08b8 | ||
|
|
0d014d5d4b | ||
|
|
937338e3ea | ||
|
|
6e506b070e | ||
|
|
847d7ea082 | ||
|
|
c8041392dc | ||
|
|
be3c75e858 | ||
|
|
02c467b6db | ||
|
|
13fe439d9f | ||
|
|
2313edd244 | ||
|
|
09c9300c21 | ||
|
|
c0e905b5d8 | ||
|
|
04da8aa8dc | ||
|
|
32f7342cd6 | ||
|
|
74afb785c2 | ||
|
|
cae1e30160 | ||
|
|
99dfe1d5af | ||
|
|
3fa654f5c6 | ||
|
|
37fde64c67 | ||
|
|
3c2d6faf68 | ||
|
|
5d30dabf97 | ||
|
|
cef1f97d18 | ||
|
|
92705723f1 | ||
|
|
af971c9b18 | ||
|
|
2f2cdd7e7c | ||
|
|
d95283f5c2 | ||
|
|
402428fb4d | ||
|
|
5600b518ac | ||
|
|
0bbb28c432 | ||
|
|
cf91e9eb58 | ||
|
|
b529baa5eb | ||
|
|
c071f66cdd | ||
|
|
92fa9c4fdc | ||
|
|
f6e0453bb2 | ||
|
|
1a07828b1b | ||
|
|
54880b2342 | ||
|
|
e32f3e71e4 | ||
|
|
0874aeee2d | ||
|
|
f1fc658a0d | ||
|
|
e3193dcabc | ||
|
|
f825a22997 | ||
|
|
815322b27a | ||
|
|
474e65f4c8 | ||
|
|
3a35281b3f | ||
|
|
ac6de50b4d | ||
|
|
a34729c153 | ||
|
|
6b2736aa63 | ||
|
|
d1584eea72 | ||
|
|
527042afd1 | ||
|
|
bd1e69e4b4 | ||
|
|
76d4c22c2d | ||
|
|
5cdc7d654a | ||
|
|
821d49943a | ||
|
|
9c0259da6b | ||
|
|
ea2c442328 | ||
|
|
b93b8cdab6 | ||
|
|
e6505fc7a4 | ||
|
|
c5c36af529 | ||
|
|
026e03c307 | ||
|
|
37b7098673 |
+19
-19
@@ -7,7 +7,7 @@ defaults: &defaults
|
||||
working_directory: ~/build
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: cimg/ruby:3.1.3-browsers
|
||||
- image: cimg/ruby:3.2.2-browsers
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: yarn
|
||||
command: yarn install --cache-folder ~/.cache/yarn
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
|
||||
# Store yarn / webpacker cache
|
||||
- save_cache:
|
||||
@@ -104,9 +104,8 @@ jobs:
|
||||
fi
|
||||
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
|
||||
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
|
||||
|
||||
|
||||
# Database setup
|
||||
- run: yarn install --check-files
|
||||
- run: bundle exec rake db:create
|
||||
- run: bundle exec rake db:schema:load
|
||||
|
||||
@@ -117,7 +116,7 @@ jobs:
|
||||
- run:
|
||||
name: Rubocop
|
||||
command: bundle exec rubocop
|
||||
|
||||
|
||||
# - run:
|
||||
# name: Brakeman
|
||||
# command: bundle exec brakeman
|
||||
@@ -126,6 +125,21 @@ jobs:
|
||||
name: eslint
|
||||
command: yarn run eslint
|
||||
|
||||
# Run frontend tests
|
||||
- run:
|
||||
name: Run frontend tests
|
||||
command: |
|
||||
mkdir -p ~/tmp/test-results/frontend_specs
|
||||
~/tmp/cc-test-reporter before-build
|
||||
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
|
||||
yarn test:coverage --profile 10 \
|
||||
--out ~/tmp/test-results/yarn.xml \
|
||||
-- ${TESTFILES}
|
||||
- run:
|
||||
name: Code Climate Test Coverage
|
||||
command: |
|
||||
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
|
||||
|
||||
# Run rails tests
|
||||
- run:
|
||||
name: Run backend tests
|
||||
@@ -145,20 +159,6 @@ jobs:
|
||||
command: |
|
||||
~/tmp/cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
|
||||
|
||||
- run:
|
||||
name: Run frontend tests
|
||||
command: |
|
||||
mkdir -p ~/tmp/test-results/frontend_specs
|
||||
~/tmp/cc-test-reporter before-build
|
||||
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
|
||||
yarn test:coverage --profile 10 \
|
||||
--out ~/tmp/test-results/yarn.xml \
|
||||
-- ${TESTFILES}
|
||||
- run:
|
||||
name: Code Climate Test Coverage
|
||||
command: |
|
||||
~/tmp/cc-test-reporter format-coverage -t lcov -o coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json buildreports/lcov.info
|
||||
|
||||
- persist_to_workspace:
|
||||
root: coverage
|
||||
paths:
|
||||
|
||||
@@ -30,7 +30,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
npm
|
||||
|
||||
# Install rbenv and ruby
|
||||
ARG RUBY_VERSION="3.1.3"
|
||||
ARG RUBY_VERSION="3.2.2"
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv \
|
||||
&& echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc \
|
||||
&& echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
||||
|
||||
@@ -173,6 +173,9 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
||||
## LogRocket
|
||||
# LOG_ROCKET_PROJECT_ID=xxxxx/some-project
|
||||
|
||||
# MICROSOFT CLARITY
|
||||
# MS_CLARITY_TOKEN=xxxxxxxxx
|
||||
|
||||
## Scout
|
||||
## https://scoutapm.com/docs/ruby/configuration
|
||||
# SCOUT_KEY=YOURKEY
|
||||
@@ -227,3 +230,6 @@ AZURE_APP_SECRET=
|
||||
## Change these values to fine tune performance
|
||||
# control the concurrency setting of sidekiq
|
||||
# SIDEKIQ_CONCURRENCY=10
|
||||
|
||||
# Sentiment analysis model file path
|
||||
SENTIMENT_FILE_PATH=
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See the error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
Share a clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Browser logs**
|
||||
|
||||
Share the browser logs to debug the issue further.
|
||||
|
||||
**Server logs**
|
||||
|
||||
Share the server logs to debug the issue further.
|
||||
|
||||
**Environment**
|
||||
|
||||
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self-hosted installation of Chatwoot. If you are using a self-hosted installation of Chatwoot, describe the type of deployment (Docker/Linux VM installation/Heroku/Kubernetes/Other).
|
||||
|
||||
- [ ] app.chatwoot.com (Chatwoot Cloud)
|
||||
- [ ] Self-hosted
|
||||
- - [ ] Linux VM
|
||||
- - [ ] Docker
|
||||
- - [ ] Kubernetes
|
||||
- - [ ] Heroku
|
||||
- - [ ] Other (Please specify)
|
||||
|
||||
|
||||
**Desktop (please complete the following information)** (If applicable)
|
||||
- OS: [e.g. Linux, Windows, MacOS]
|
||||
- Browser [e.g. chrome, firefox, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information)** (If applicable)
|
||||
- Device: [e.g. iPhone6, Pixel7]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, firefox, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Docker** (If applicable)
|
||||
|
||||
Please share the output of the following.
|
||||
- `docker version`
|
||||
- `docker info`
|
||||
- `docker-compose version`
|
||||
|
||||
**Cloud Provider** (If applicable)
|
||||
- [ ] AWS
|
||||
- [ ] GCP
|
||||
- [ ] Azure
|
||||
- [ ] DigitalOcean
|
||||
- [ ] Others
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,78 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a report to help us improve
|
||||
labels: 'Bug'
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A concise description of what you expected to happen along with screenshots if applicable.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self-hosted installation of Chatwoot. If you are using a self-hosted installation of Chatwoot, describe the type of deployment (Docker/Linux VM installation/Heroku/Kubernetes/Other).
|
||||
options:
|
||||
- app.chatwoot.com
|
||||
- Linux VM
|
||||
- Docker
|
||||
- Kubernetes
|
||||
- Heroku
|
||||
- Other [please specify in the description]
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: provider
|
||||
attributes:
|
||||
label: Cloud Provider
|
||||
description:
|
||||
options:
|
||||
- AWS
|
||||
- GCP
|
||||
- Azure
|
||||
- DigitalOcean
|
||||
- Other [please specify in the description]
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Describe the platform you are using
|
||||
options:
|
||||
- Browser
|
||||
- Mobile
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: The operating system and the version you are using.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Browser and version
|
||||
description: The name of the browser and version you are using.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Docker (if applicable)
|
||||
description: |
|
||||
Please share the output of the following.
|
||||
- `docker version`
|
||||
- `docker info`
|
||||
- `docker-compose version`
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Report a security issue
|
||||
url: https://www.chatwoot.com/docs/contributing-guide/security-reports/
|
||||
about: Guidelines and steps to report a security vulnerability. Please report security vulnerabilities here.
|
||||
- name: Product Documentation
|
||||
url: https://www.chatwoot.com/help-center
|
||||
about: If you have questions, are confused, or just want to understand our product better, please check out our documentation.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Enhancement request
|
||||
about: Suggest any enhancements for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your enhancement request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the enhancement request here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,28 @@
|
||||
name: 🧙 Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: 'feature-request'
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature or enhancement related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,23 @@
|
||||
# ref: https://github.com/amannn/action-semantic-pull-request
|
||||
# ensure PR title is in semantic format
|
||||
|
||||
name: "Lint PR"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Run Size Limit Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
|
||||
- name: Strip enterprise code
|
||||
run: |
|
||||
rm -rf enterprise
|
||||
rm -rf spec/enterprise
|
||||
|
||||
- name: Run asset compile
|
||||
run: bundle exec rake assets:precompile
|
||||
|
||||
- name: Size Check
|
||||
run: yarn run size
|
||||
@@ -67,3 +67,12 @@ test/cypress/videos/*
|
||||
# yalc for local testing
|
||||
.yalc
|
||||
yalc.lock
|
||||
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
.yarn-integrity
|
||||
|
||||
/storybook-static
|
||||
@@ -71,6 +71,10 @@ Rails/ApplicationController:
|
||||
- 'app/controllers/platform_controller.rb'
|
||||
- 'app/controllers/public_controller.rb'
|
||||
- 'app/controllers/survey/responses_controller.rb'
|
||||
Rails/FindEach:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'app/**/*.rb'
|
||||
Rails/CompactBlank:
|
||||
Enabled: false
|
||||
Rails/EnvironmentVariableAccess:
|
||||
@@ -158,8 +162,33 @@ Rails/RenderInline:
|
||||
Performance/CollectionLiteralInLoop:
|
||||
Exclude:
|
||||
- 'db/migrate/20210315101919_enable_email_channel.rb'
|
||||
Rails/ThreeStateBooleanColumn:
|
||||
Exclude:
|
||||
- 'db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb'
|
||||
- 'db/migrate/20200605130625_agent_away_message_to_auto_reply.rb'
|
||||
- 'db/migrate/20200606132552_create_labels.rb'
|
||||
- 'db/migrate/20201027135006_create_working_hours.rb'
|
||||
- 'db/migrate/20210112174124_add_hmac_token_to_inbox.rb'
|
||||
- 'db/migrate/20210114202310_create_teams.rb'
|
||||
- 'db/migrate/20210212154240_add_request_for_email_on_channel_web_widget.rb'
|
||||
- 'db/migrate/20210428135041_add_campaigns.rb'
|
||||
- 'db/migrate/20210602182058_add_hmac_to_api_channel.rb'
|
||||
- 'db/migrate/20210609133433_add_email_collect_to_inboxes.rb'
|
||||
- 'db/migrate/20210618095823_add_csat_toggle_for_inbox.rb'
|
||||
- 'db/migrate/20210927062350_add_trigger_only_during_business_hours_collect_to_campaigns.rb'
|
||||
- 'db/migrate/20211027073553_add_imap_smtp_config_to_channel_email.rb'
|
||||
- 'db/migrate/20211109143122_add_tweet_enabled_flag_to_twitter_channel.rb'
|
||||
- 'db/migrate/20211216110209_add_allow_messages_after_resolved_to_inbox.rb'
|
||||
- 'db/migrate/20220116103902_add_open_ssl_verify_mode_to_channel_email.rb'
|
||||
- 'db/migrate/20220216151613_add_open_all_day_to_working_hour.rb'
|
||||
- 'db/migrate/20220511072655_add_archive_column_to_portal.rb'
|
||||
- 'db/migrate/20230503101201_create_sla_policies.rb'
|
||||
RSpec/IndexedLet:
|
||||
Enabled: false
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
|
||||
# we should bring this down
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 14
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.1.3
|
||||
3.2.2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/spec
|
||||
@@ -1,10 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.1.3'
|
||||
ruby '3.2.2'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
gem 'rails', '~> 6.1', '>= 6.1.7.3'
|
||||
gem 'rails', '~> 7.0.5.1'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
@@ -36,19 +36,19 @@ gem 'json_schemer'
|
||||
# Rack middleware for blocking & throttling abusive requests
|
||||
gem 'rack-attack'
|
||||
# a utility tool for streaming, flexible and safe downloading of remote files
|
||||
gem 'down', '~> 5.0'
|
||||
gem 'down'
|
||||
# authentication type to fetch and send mail over oauth2.0
|
||||
gem 'gmail_xoauth'
|
||||
# Prevent CSV injection
|
||||
gem 'csv-safe'
|
||||
# Support message translation
|
||||
gem 'google-cloud-translate'
|
||||
|
||||
##-- for active storage --##
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'azure-storage-blob', require: false
|
||||
# original gem isn't maintained actively
|
||||
# we wanted updated version of faraday which is a dependency for slack-ruby-client
|
||||
gem 'azure-storage-blob', git: 'https://github.com/chatwoot/azure-storage-ruby', branch: 'chatwoot', require: false
|
||||
gem 'google-cloud-storage', require: false
|
||||
gem 'image_processing', '~> 1.12.2'
|
||||
gem 'image_processing'
|
||||
|
||||
##-- gems for database --#
|
||||
gem 'groupdate'
|
||||
@@ -62,26 +62,26 @@ gem 'activerecord-import'
|
||||
gem 'dotenv-rails'
|
||||
gem 'foreman'
|
||||
gem 'puma'
|
||||
gem 'webpacker', '~> 5.4', '>= 5.4.3'
|
||||
gem 'webpacker'
|
||||
# metrics on heroku
|
||||
gem 'barnes'
|
||||
|
||||
##--- gems for authentication & authorization ---##
|
||||
gem 'devise'
|
||||
gem 'devise-secure_password', '~> 2.0', git: 'https://github.com/chatwoot/devise-secure_password'
|
||||
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
|
||||
gem 'devise_token_auth'
|
||||
# authorization
|
||||
gem 'jwt'
|
||||
gem 'pundit'
|
||||
# super admin
|
||||
gem 'administrate'
|
||||
gem 'administrate-field-active_storage'
|
||||
|
||||
##--- gems for pubsub service ---##
|
||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||
gem 'wisper', '2.0.0'
|
||||
|
||||
##--- gems for channels ---##
|
||||
# TODO: bump up gem to 2.0
|
||||
gem 'facebook-messenger'
|
||||
gem 'line-bot-api'
|
||||
gem 'twilio-ruby', '~> 5.66'
|
||||
@@ -91,9 +91,14 @@ gem 'twitty', '~> 0.1.5'
|
||||
# facebook client
|
||||
gem 'koala'
|
||||
# slack client
|
||||
gem 'slack-ruby-client'
|
||||
gem 'slack-ruby-client', '~> 2.0.0'
|
||||
# for dialogflow integrations
|
||||
gem 'google-cloud-dialogflow'
|
||||
gem 'google-cloud-dialogflow-v2'
|
||||
gem 'grpc'
|
||||
# Translate integrations
|
||||
# 'google-cloud-translate' gem depends on faraday 2.0 version
|
||||
# this dependency breaks the slack-ruby-client gem
|
||||
gem 'google-cloud-translate-v3'
|
||||
|
||||
##-- apm and error monitoring ---#
|
||||
# loaded only when environment variables are set.
|
||||
@@ -108,9 +113,9 @@ gem 'sentry-ruby', require: false
|
||||
gem 'sentry-sidekiq', require: false
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq', '~> 6.4.2'
|
||||
gem 'sidekiq'
|
||||
# We want cron jobs
|
||||
gem 'sidekiq-cron', '~> 1.6', '>= 1.6.0'
|
||||
gem 'sidekiq-cron'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
@@ -129,7 +134,10 @@ gem 'procore-sift'
|
||||
|
||||
# parse email
|
||||
gem 'email_reply_trimmer'
|
||||
gem 'html2text'
|
||||
|
||||
# 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'
|
||||
|
||||
# to calculate working hours
|
||||
gem 'working_hours'
|
||||
@@ -144,24 +152,22 @@ gem 'stripe'
|
||||
## to populate db with sample data
|
||||
gem 'faker'
|
||||
|
||||
# Can remove this in rails 7
|
||||
gem 'net-imap', require: false
|
||||
gem 'net-pop', require: false
|
||||
gem 'net-smtp', require: false
|
||||
|
||||
# Include logrange conditionally in intializer using env variable
|
||||
gem 'lograge', '~> 0.12.0', require: false
|
||||
|
||||
# worked with microsoft refresh token
|
||||
gem 'omniauth-oauth2'
|
||||
|
||||
gem 'audited', '~> 5.2'
|
||||
gem 'audited', '~> 5.3'
|
||||
|
||||
# need for google auth
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-google-oauth2'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
|
||||
# Sentiment analysis
|
||||
gem 'informers'
|
||||
|
||||
### Gems required only in specific deployment environments ###
|
||||
##############################################################
|
||||
|
||||
@@ -174,6 +180,7 @@ group :development do
|
||||
gem 'annotate'
|
||||
gem 'bullet'
|
||||
gem 'letter_opener'
|
||||
gem 'scss_lint', require: false
|
||||
gem 'web-console'
|
||||
|
||||
# used in swagger build
|
||||
@@ -189,7 +196,7 @@ end
|
||||
|
||||
group :test do
|
||||
# Cypress in rails.
|
||||
gem 'cypress-on-rails', '~> 1.13', '>= 1.13.1'
|
||||
gem 'cypress-on-rails'
|
||||
# fast cleaning of database
|
||||
gem 'database_cleaner'
|
||||
# mock http calls
|
||||
@@ -211,7 +218,7 @@ group :development, :test do
|
||||
gem 'mock_redis'
|
||||
gem 'pry-rails'
|
||||
gem 'rspec_junit_formatter'
|
||||
gem 'rspec-rails', '~> 5.0.3'
|
||||
gem 'rspec-rails'
|
||||
gem 'rubocop', require: false
|
||||
gem 'rubocop-performance', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
|
||||
+372
-334
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
# Variables
|
||||
APP_NAME := chatwoot
|
||||
RAILS_ENV ?= development
|
||||
|
||||
# Targets
|
||||
setup:
|
||||
gem install bundler
|
||||
bundle install
|
||||
yarn install
|
||||
|
||||
db_create:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:create
|
||||
|
||||
db_migrate:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:migrate
|
||||
|
||||
db_seed:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:seed
|
||||
|
||||
db:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:chatwoot_prepare
|
||||
|
||||
console:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails console
|
||||
|
||||
server:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails server -b 0.0.0.0 -p 3000
|
||||
|
||||
burn:
|
||||
bundle && yarn
|
||||
|
||||
run:
|
||||
overmind start -f Procfile.dev
|
||||
|
||||
docker:
|
||||
docker build -t $(APP_NAME) -f ./docker/Dockerfile .
|
||||
|
||||
.PHONY: setup db_create db_migrate db_seed db console server burn docker run
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.2.0
|
||||
2.17.0
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.1.0
|
||||
2.3.0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//= link_tree ../images
|
||||
//= link administrate/application.css
|
||||
//= link administrate/application.js
|
||||
//= link administrate-field-active_storage/application.css
|
||||
//= link dashboardChart.js
|
||||
//= link secretField.js
|
||||
|
||||
@@ -11,7 +11,7 @@ function prepareData(data) {
|
||||
|
||||
function getChartOptions() {
|
||||
var fontFamily =
|
||||
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
||||
'PlusJakarta,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
||||
return {
|
||||
responsive: true,
|
||||
legend: { labels: { fontFamily } },
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Typography
|
||||
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif !default;
|
||||
$heading-font-family: $base-font-family !default;
|
||||
|
||||
$base-font-size: 10px !default;
|
||||
$base-font-size: 16px !default;
|
||||
|
||||
$base-line-height: 1.5 !default;
|
||||
$heading-line-height: 1.2 !default;
|
||||
|
||||
@@ -63,28 +63,43 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
@conversation ||= Conversation.where(conversation_params).find_by(
|
||||
"additional_attributes ->> 'type' = 'instagram_direct_message'"
|
||||
) || build_conversation
|
||||
end
|
||||
|
||||
def message_content
|
||||
@messaging[:message][:text]
|
||||
end
|
||||
|
||||
def story_reply_attributes
|
||||
message[:reply_to][:story] if message[:reply_to].present? && message[:reply_to][:story].present?
|
||||
end
|
||||
|
||||
def build_message
|
||||
return if @outgoing_echo && already_sent_from_chatwoot?
|
||||
return if message_content.blank? && all_unsupported_files?
|
||||
|
||||
@message = conversation.messages.create!(message_params)
|
||||
save_story_id
|
||||
|
||||
attachments.each do |attachment|
|
||||
process_attachment(attachment)
|
||||
end
|
||||
end
|
||||
|
||||
def save_story_id
|
||||
return if story_reply_attributes.blank?
|
||||
|
||||
@message.save_story_info(story_reply_attributes)
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
|
||||
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: { type: 'instagram_direct_message' }
|
||||
))
|
||||
end
|
||||
|
||||
@@ -92,10 +107,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id,
|
||||
additional_attributes: {
|
||||
type: 'instagram_direct_message'
|
||||
}
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -48,17 +48,22 @@ class Messages::MessageBuilder
|
||||
def process_emails
|
||||
return unless @conversation.inbox&.inbox_type == 'Email'
|
||||
|
||||
cc_emails = []
|
||||
cc_emails = @params[:cc_emails].split(',') if @params[:cc_emails].present?
|
||||
cc_emails = process_email_string(@params[:cc_emails])
|
||||
bcc_emails = process_email_string(@params[:bcc_emails])
|
||||
to_emails = process_email_string(@params[:to_emails])
|
||||
|
||||
bcc_emails = []
|
||||
bcc_emails = @params[:bcc_emails].split(',') if @params[:bcc_emails].present?
|
||||
|
||||
all_email_addresses = cc_emails + bcc_emails
|
||||
all_email_addresses = cc_emails + bcc_emails + to_emails
|
||||
validate_email_addresses(all_email_addresses)
|
||||
|
||||
@message.content_attributes[:cc_emails] = cc_emails
|
||||
@message.content_attributes[:bcc_emails] = bcc_emails
|
||||
@message.content_attributes[:to_emails] = to_emails
|
||||
end
|
||||
|
||||
def process_email_string(email_string)
|
||||
return [] if email_string.blank?
|
||||
|
||||
email_string.gsub(/\s+/, '').split(',')
|
||||
end
|
||||
|
||||
def validate_email_addresses(all_emails)
|
||||
|
||||
@@ -96,10 +96,9 @@ class V2::ReportBuilder
|
||||
|
||||
def conversations
|
||||
@open_conversations = scope.conversations.where(account_id: @account.id).open
|
||||
first_response_count = @account.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
|
||||
metric = {
|
||||
open: @open_conversations.count,
|
||||
unattended: @open_conversations.count - first_response_count
|
||||
unattended: @open_conversations.unattended.count
|
||||
}
|
||||
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
||||
metric
|
||||
|
||||
@@ -16,6 +16,9 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
end
|
||||
|
||||
def show; end
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@article = @portal.articles.create!(article_params)
|
||||
@article.associate_root_article(article_params[:associated_article_id])
|
||||
@@ -23,10 +26,6 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
render json: { error: @article.errors.messages }, status: :unprocessable_entity and return unless @article.valid?
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@article.update!(article_params) if params[:article].present?
|
||||
render json: { error: @article.errors.messages }, status: :unprocessable_entity and return unless @article.valid?
|
||||
|
||||
@@ -6,6 +6,8 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
||||
@automation_rules = Current.account.automation_rules
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@automation_rule = Current.account.automation_rules.new(automation_rules_permit)
|
||||
@automation_rule.actions = params[:actions]
|
||||
@@ -28,8 +30,6 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
automation_rule_update
|
||||
|
||||
@@ -6,21 +6,21 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
|
||||
@campaigns = Current.account.campaigns
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@campaign = Current.account.campaigns.create!(campaign_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@campaign.update!(campaign_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@campaign.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@campaign.update!(campaign_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def campaign
|
||||
|
||||
@@ -9,6 +9,8 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
|
||||
@categories = @portal.categories.search(params)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@category = @portal.categories.create!(category_params)
|
||||
@category.related_categories << related_categories_records
|
||||
@@ -17,8 +19,6 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
|
||||
@category.save!
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@category.update!(category_params)
|
||||
@category.related_categories = related_categories_records if related_categories_records.any?
|
||||
|
||||
@@ -18,7 +18,11 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
|
||||
end
|
||||
|
||||
def authenticate_twilio
|
||||
client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
|
||||
client = if permitted_params[:api_key_sid].present?
|
||||
Twilio::REST::Client.new(permitted_params[:api_key_sid], permitted_params[:auth_token], permitted_params[:account_sid])
|
||||
else
|
||||
Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
|
||||
end
|
||||
client.messages.list(limit: 1)
|
||||
end
|
||||
|
||||
@@ -40,6 +44,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
|
||||
@twilio_channel = Current.account.twilio_sms.create!(
|
||||
account_sid: permitted_params[:account_sid],
|
||||
auth_token: permitted_params[:auth_token],
|
||||
api_key_sid: permitted_params[:api_key_sid],
|
||||
messaging_service_sid: permitted_params[:messaging_service_sid].presence,
|
||||
phone_number: phone_number,
|
||||
medium: medium
|
||||
@@ -52,7 +57,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
|
||||
|
||||
def permitted_params
|
||||
params.require(:twilio_channel).permit(
|
||||
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium
|
||||
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium, :api_key_sid
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::Contacts::BaseController
|
||||
include HmacConcern
|
||||
before_action :ensure_inbox, only: [:create]
|
||||
|
||||
def create
|
||||
@contact_inbox = ContactInboxBuilder.new(
|
||||
contact: @contact,
|
||||
inbox: @inbox,
|
||||
source_id: params[:source_id]
|
||||
source_id: params[:source_id],
|
||||
hmac_verified: hmac_verified?
|
||||
).perform
|
||||
end
|
||||
|
||||
|
||||
@@ -5,21 +5,21 @@ class Api::V1::Accounts::Contacts::NotesController < Api::V1::Accounts::Contacts
|
||||
@notes = @contact.notes.latest.includes(:user)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@note = @contact.notes.create!(note_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@note.update(note_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@note.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@note.update(note_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def note
|
||||
|
||||
@@ -42,6 +42,12 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def export
|
||||
column_names = params['column_names']
|
||||
Account::ContactsExportJob.perform_later(Current.account.id, column_names)
|
||||
head :ok, message: I18n.t('errors.contacts.export.success')
|
||||
end
|
||||
|
||||
# returns online contacts
|
||||
def active
|
||||
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
|
||||
include EnsureCurrentAccountHelper
|
||||
before_action :conversation
|
||||
|
||||
private
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
include Events::Types
|
||||
include DateRangeHelper
|
||||
include HmacConcern
|
||||
|
||||
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
|
||||
before_action :inbox, :contact, :contact_inbox, only: [:create]
|
||||
@@ -22,6 +23,12 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def attachments
|
||||
@attachments = @conversation.attachments
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
@conversation = ConversationBuilder.new(params: params, contact_inbox: @contact_inbox).perform
|
||||
@@ -29,8 +36,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
end
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def filter
|
||||
result = ::Conversations::FilterService.new(params.permit!, current_user).perform
|
||||
@conversations = result[:conversations]
|
||||
@@ -64,13 +69,14 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
|
||||
end
|
||||
|
||||
def toggle_priority
|
||||
@conversation.toggle_priority(params[:priority])
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_typing_status
|
||||
case params[:typing_status]
|
||||
when 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON, params[:is_private])
|
||||
when 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF, params[:is_private])
|
||||
end
|
||||
typing_status_manager = ::Conversations::TypingStatusManager.new(@conversation, current_user, params)
|
||||
typing_status_manager.toggle_typing_status
|
||||
head :ok
|
||||
end
|
||||
|
||||
@@ -99,9 +105,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
end
|
||||
|
||||
def set_conversation_status
|
||||
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
|
||||
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
|
||||
# status = params[:status] == 'bot' ? 'pending' : params[:status]
|
||||
@conversation.status = params[:status]
|
||||
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
|
||||
end
|
||||
@@ -111,11 +114,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
@conversation.update_assignee(@agent)
|
||||
end
|
||||
|
||||
def trigger_typing_event(event, is_private)
|
||||
user = current_user.presence || @resource
|
||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Current.account.conversations.find_by!(display_id: params[:id])
|
||||
authorize @conversation.inbox, :show?
|
||||
@@ -152,7 +150,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
ContactInboxBuilder.new(
|
||||
contact: @contact,
|
||||
inbox: @inbox,
|
||||
source_id: params[:source_id]
|
||||
source_id: params[:source_id],
|
||||
hmac_verified: hmac_verified?
|
||||
).perform
|
||||
end
|
||||
|
||||
|
||||
@@ -34,9 +34,12 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
|
||||
end
|
||||
|
||||
def set_csat_survey_responses
|
||||
@csat_survey_responses = filtrate(
|
||||
Current.account.csat_survey_responses.includes([:conversation, :assigned_agent, :contact])
|
||||
).filter_by_created_at(range).filter_by_assigned_agent_id(params[:user_ids])
|
||||
base_query = Current.account.csat_survey_responses.includes([:conversation, :assigned_agent, :contact])
|
||||
@csat_survey_responses = filtrate(base_query).filter_by_created_at(range)
|
||||
.filter_by_assigned_agent_id(params[:user_ids])
|
||||
.filter_by_inbox_id(params[:inbox_id])
|
||||
.filter_by_team_id(params[:team_id])
|
||||
.filter_by_rating(params[:rating])
|
||||
end
|
||||
|
||||
def set_current_page_surveys
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_custom_filters, except: [:create]
|
||||
before_action :fetch_custom_filter, only: [:show, :update, :destroy]
|
||||
DEFAULT_FILTER_TYPE = 'conversation'.freeze
|
||||
@@ -11,6 +12,7 @@ class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseContro
|
||||
@custom_filter = current_user.custom_filters.create!(
|
||||
permitted_payload.merge(account_id: Current.account.id)
|
||||
)
|
||||
render json: { error: @custom_filter.errors.messages }, status: :unprocessable_entity and return unless @custom_filter.valid?
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -2,6 +2,11 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
|
||||
before_action :fetch_inbox
|
||||
before_action :current_agents_ids, only: [:create, :update]
|
||||
|
||||
def show
|
||||
authorize @inbox, :show?
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def create
|
||||
authorize @inbox, :create?
|
||||
ActiveRecord::Base.transaction do
|
||||
@@ -10,11 +15,6 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @inbox, :show?
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @inbox, :update?
|
||||
update_agents_list
|
||||
|
||||
@@ -44,26 +44,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
def update
|
||||
@inbox.update!(permitted_params.except(:channel))
|
||||
update_inbox_working_hours
|
||||
channel_attributes = get_channel_attributes(@inbox.channel_type)
|
||||
|
||||
# Inbox update doesn't necessarily need channel attributes
|
||||
return if permitted_params(channel_attributes)[:channel].blank?
|
||||
|
||||
if @inbox.inbox_type == 'Email'
|
||||
begin
|
||||
validate_email_channel(channel_attributes)
|
||||
rescue StandardError => e
|
||||
render json: { message: e }, status: :unprocessable_entity and return
|
||||
end
|
||||
@inbox.channel.reauthorized!
|
||||
end
|
||||
|
||||
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
|
||||
update_channel_feature_flags
|
||||
end
|
||||
|
||||
def update_inbox_working_hours
|
||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||
update_channel if channel_update_required?
|
||||
end
|
||||
|
||||
def agent_bot
|
||||
@@ -82,7 +63,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
|
||||
def destroy
|
||||
::DeleteObjectJob.perform_later(@inbox) if @inbox.present?
|
||||
::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present?
|
||||
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
|
||||
end
|
||||
|
||||
@@ -103,6 +84,35 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
end
|
||||
|
||||
def update_inbox_working_hours
|
||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||
end
|
||||
|
||||
def update_channel
|
||||
channel_attributes = get_channel_attributes(@inbox.channel_type)
|
||||
return if permitted_params(channel_attributes)[:channel].blank?
|
||||
|
||||
validate_and_update_email_channel(channel_attributes) if @inbox.inbox_type == 'Email'
|
||||
|
||||
reauthorize_and_update_channel(channel_attributes)
|
||||
update_channel_feature_flags
|
||||
end
|
||||
|
||||
def channel_update_required?
|
||||
permitted_params(get_channel_attributes(@inbox.channel_type))[:channel].present?
|
||||
end
|
||||
|
||||
def validate_and_update_email_channel(channel_attributes)
|
||||
validate_email_channel(channel_attributes)
|
||||
rescue StandardError => e
|
||||
render json: { message: e }, status: :unprocessable_entity and return
|
||||
end
|
||||
|
||||
def reauthorize_and_update_channel(channel_attributes)
|
||||
@inbox.channel.reauthorized! if @inbox.channel.respond_to?(:reauthorized!)
|
||||
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
|
||||
end
|
||||
|
||||
def update_channel_feature_flags
|
||||
return unless @inbox.web_widget?
|
||||
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
||||
@@ -114,10 +124,13 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
def inbox_attributes
|
||||
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
||||
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
|
||||
:lock_to_single_conversation]
|
||||
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name]
|
||||
end
|
||||
|
||||
def permitted_params(channel_attributes = [])
|
||||
# We will remove this line after fixing https://linear.app/chatwoot/issue/CW-1567/null-value-passed-as-null-string-to-backend
|
||||
params.each { |k, v| params[k] = params[k] == 'null' ? nil : v }
|
||||
|
||||
params.permit(
|
||||
*inbox_attributes,
|
||||
channel: [:type, *channel_attributes]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_hook, only: [:update, :destroy]
|
||||
before_action :fetch_hook, except: [:create]
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
@@ -10,6 +10,10 @@ class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::Base
|
||||
@hook.update!(permitted_params.slice(:status, :settings))
|
||||
end
|
||||
|
||||
def process_event
|
||||
render json: { message: @hook.process_event(params[:event]) }
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy!
|
||||
head :ok
|
||||
|
||||
@@ -6,6 +6,10 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||
@macros = Macro.with_visibility(current_user, params)
|
||||
end
|
||||
|
||||
def show
|
||||
head :not_found if @macro.nil?
|
||||
end
|
||||
|
||||
def create
|
||||
@macro = Current.account.macros.new(macros_with_user.merge(created_by_id: current_user.id))
|
||||
@macro.set_visibility(current_user, permitted_params)
|
||||
@@ -18,8 +22,16 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||
@macro
|
||||
end
|
||||
|
||||
def show
|
||||
head :not_found if @macro.nil?
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
@macro.update!(macros_with_user)
|
||||
@macro.set_visibility(current_user, permitted_params)
|
||||
process_attachments
|
||||
@macro.save!
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e
|
||||
render json: { error: @macro.errors.messages }.to_json, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -37,18 +49,6 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
@macro.update!(macros_with_user)
|
||||
@macro.set_visibility(current_user, permitted_params)
|
||||
process_attachments
|
||||
@macro.save!
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e
|
||||
render json: { error: @macro.errors.messages }.to_json, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def execute
|
||||
::MacrosExecutionJob.perform_later(@macro, conversation_ids: params[:conversation_ids], user: Current.user)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||
ActiveRecord::Base.transaction do
|
||||
@portal.update!(portal_params) if params[:portal].present?
|
||||
# @portal.custom_domain = parsed_custom_domain
|
||||
process_attached_logo
|
||||
process_attached_logo if params[:blob_id].present?
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e
|
||||
render json: { error: @portal.errors.messages }.to_json, status: :unprocessable_entity
|
||||
|
||||
@@ -14,6 +14,11 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
CustomExceptions::Account::UserErrors,
|
||||
with: :render_error_response
|
||||
|
||||
def show
|
||||
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
|
||||
render 'api/v1/accounts/show', format: :json
|
||||
end
|
||||
|
||||
def create
|
||||
@user, @account = AccountBuilder.new(
|
||||
account_name: account_params[:account_name],
|
||||
@@ -32,14 +37,10 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def cache_keys
|
||||
expires_in 10.seconds, public: false, stale_while_revalidate: 5.minutes
|
||||
render json: { cache_keys: get_cache_keys }, status: :ok
|
||||
end
|
||||
|
||||
def show
|
||||
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
|
||||
render 'api/v1/accounts/show', format: :json
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :auto_resolve_duration))
|
||||
end
|
||||
|
||||
@@ -17,7 +17,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
@message.update!(submitted_email: contact_email)
|
||||
ContactIdentifyAction.new(
|
||||
contact: @contact,
|
||||
params: { email: contact_email, name: contact_name }
|
||||
params: { email: contact_email, name: contact_name },
|
||||
retain_original_contact_name: true
|
||||
).perform
|
||||
else
|
||||
@message.update!(message_update_params[:message])
|
||||
@@ -48,7 +49,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
def message_finder_params
|
||||
{
|
||||
filter_internal_messages: true,
|
||||
before: permitted_params[:before]
|
||||
before: permitted_params[:before],
|
||||
after: permitted_params[:after]
|
||||
}
|
||||
end
|
||||
|
||||
@@ -62,7 +64,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
|
||||
def permitted_params
|
||||
# timestamp parameter is used in create conversation method
|
||||
params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
params.permit(:id, :before, :after, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
end
|
||||
|
||||
def set_message
|
||||
|
||||
@@ -72,14 +72,16 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
def current_summary_params
|
||||
common_params.merge({
|
||||
since: range[:current][:since],
|
||||
until: range[:current][:until]
|
||||
until: range[:current][:until],
|
||||
timezone_offset: params[:timezone_offset]
|
||||
})
|
||||
end
|
||||
|
||||
def previous_summary_params
|
||||
common_params.merge({
|
||||
since: range[:previous][:since],
|
||||
until: range[:previous][:until]
|
||||
until: range[:previous][:until],
|
||||
timezone_offset: params[:timezone_offset]
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ class ApiController < ApplicationController
|
||||
|
||||
def index
|
||||
render json: { version: Chatwoot.config[:version],
|
||||
timestamp: Time.now.utc.to_formatted_s(:db),
|
||||
timestamp: Time.now.utc.to_fs(:db),
|
||||
queue_services: redis_status,
|
||||
data_services: postgres_status }
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module HmacConcern
|
||||
def hmac_verified?
|
||||
ActiveModel::Type::Boolean.new.cast(params[:hmac_verified]).present?
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
class DashboardController < ActionController::Base
|
||||
include SwitchLocale
|
||||
|
||||
before_action :set_application_pack
|
||||
before_action :set_global_config
|
||||
around_action :switch_locale
|
||||
before_action :ensure_installation_onboarding, only: [:index]
|
||||
@@ -14,7 +15,7 @@ class DashboardController < ActionController::Base
|
||||
|
||||
def set_global_config
|
||||
@global_config = GlobalConfig.get(
|
||||
'LOGO', 'LOGO_THUMBNAIL',
|
||||
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
|
||||
'INSTALLATION_NAME',
|
||||
'WIDGET_BRAND_URL', 'TERMS_URL',
|
||||
'PRIVACY_URL',
|
||||
@@ -60,4 +61,12 @@ class DashboardController < ActionController::Base
|
||||
GIT_SHA: GIT_HASH
|
||||
}
|
||||
end
|
||||
|
||||
def set_application_pack
|
||||
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
|
||||
'v3app'
|
||||
else
|
||||
'application'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,16 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
|
||||
skip_before_action :require_no_authentication, raise: false
|
||||
skip_before_action :authenticate_user!, raise: false
|
||||
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user
|
||||
@user.send_reset_password_instructions
|
||||
build_response(I18n.t('messages.reset_password_success'), 200)
|
||||
else
|
||||
build_response(I18n.t('messages.reset_password_failure'), 404)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# params: reset_password_token, password, password_confirmation
|
||||
original_token = params[:reset_password_token]
|
||||
@@ -17,16 +27,6 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user
|
||||
@user.send_reset_password_instructions
|
||||
build_response(I18n.t('messages.reset_password_success'), 200)
|
||||
else
|
||||
build_response(I18n.t('messages.reset_password_failure'), 404)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reset_password_and_confirmation(recoverable)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsController
|
||||
class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
|
||||
# Prevent session parameter from being passed
|
||||
# Unpermitted parameter: session
|
||||
wrap_parameters format: []
|
||||
@@ -37,3 +37,5 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle
|
||||
@resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token])
|
||||
end
|
||||
end
|
||||
|
||||
DeviseOverrides::SessionsController.prepend_mod_with('DeviseOverrides::SessionsController')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class DeviseOverrides::TokenValidationsController < ::DeviseTokenAuth::TokenValidationsController
|
||||
class DeviseOverrides::TokenValidationsController < DeviseTokenAuth::TokenValidationsController
|
||||
def validate_token
|
||||
# @resource will have been set by set_user_by_token concern
|
||||
if @resource
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
class Platform::Api::V1::AccountsController < PlatformController
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@resource = Account.create!(account_params)
|
||||
update_resource_features
|
||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@resource.assign_attributes(account_params)
|
||||
update_resource_features
|
||||
|
||||
@@ -6,14 +6,14 @@ class Platform::Api::V1::AgentBotsController < PlatformController
|
||||
@resources = @platform_app.platform_app_permissibles.where(permissible_type: 'AgentBot').all
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@resource = AgentBot.new(agent_bot_params)
|
||||
@resource.save!
|
||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@resource.update!(agent_bot_params)
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ class Platform::Api::V1::UsersController < PlatformController
|
||||
before_action(only: [:login]) { set_resource }
|
||||
before_action(only: [:login]) { validate_platform_app_permissible }
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
|
||||
@resource.skip_confirmation!
|
||||
@@ -16,8 +18,6 @@ class Platform::Api::V1::UsersController < PlatformController
|
||||
render json: { url: @resource.generate_sso_link }
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@resource.assign_attributes(user_update_params)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
|
||||
before_action :contact_inbox, except: [:create]
|
||||
before_action :process_hmac
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
@contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||
@@ -11,8 +13,6 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
|
||||
).perform
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
contact_identify_action = ContactIdentifyAction.new(
|
||||
contact: @contact_inbox.contact,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::BaseController
|
||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||
before_action :portal
|
||||
before_action :set_category, except: [:index]
|
||||
before_action :set_category, except: [:index, :show]
|
||||
before_action :set_article, only: [:show]
|
||||
layout 'portal'
|
||||
|
||||
@@ -16,7 +16,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
private
|
||||
|
||||
def set_article
|
||||
@article = @category.articles.find(permitted_params[:id])
|
||||
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
|
||||
@article.increment_view_count
|
||||
@parsed_content = render_article_content(@article.content)
|
||||
end
|
||||
@@ -39,12 +39,10 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:slug, :category_slug, :locale, :id)
|
||||
params.permit(:slug, :category_slug, :locale, :id, :article_slug)
|
||||
end
|
||||
|
||||
def render_article_content(content)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
CommonMarker.render_html(content).html_safe
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
ChatwootMarkdownRenderer.new(content).render_article
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,9 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
|
||||
def set_locale(&)
|
||||
switch_locale_with_portal(&) if params[:locale].present?
|
||||
switch_locale_with_article(&) if params[:article_slug].present?
|
||||
|
||||
yield
|
||||
end
|
||||
|
||||
def switch_locale_with_portal(&)
|
||||
@@ -19,4 +22,16 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
|
||||
def switch_locale_with_article(&)
|
||||
article = Article.find_by(slug: params[:article_slug])
|
||||
|
||||
@locale = if article.category.present?
|
||||
article.category.locale
|
||||
else
|
||||
'en'
|
||||
end
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,13 +45,24 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
|
||||
|
||||
def seed
|
||||
Internal::SeedAccountJob.perform_later(requested_resource)
|
||||
# rubocop:disable Rails/I18nLocaleTexts
|
||||
redirect_back(fallback_location: [namespace, requested_resource], notice: 'Account seeding triggered')
|
||||
# rubocop:enable Rails/I18nLocaleTexts
|
||||
end
|
||||
|
||||
def reset_cache
|
||||
requested_resource.reset_cache_keys
|
||||
# rubocop:disable Rails/I18nLocaleTexts
|
||||
redirect_back(fallback_location: [namespace, requested_resource], notice: 'Cache keys cleared')
|
||||
# rubocop:enable Rails/I18nLocaleTexts
|
||||
end
|
||||
|
||||
def destroy
|
||||
account = Account.find(params[:id])
|
||||
|
||||
DeleteObjectJob.perform_later(account) if account.present?
|
||||
# rubocop:disable Rails/I18nLocaleTexts
|
||||
redirect_back(fallback_location: [namespace, requested_resource], notice: 'Account deletion is in progress.')
|
||||
# rubocop:enable Rails/I18nLocaleTexts
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,4 +41,14 @@ class SuperAdmin::AgentBotsController < SuperAdmin::ApplicationController
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
|
||||
def destroy_avatar
|
||||
avatar = requested_resource.avatar
|
||||
avatar.purge
|
||||
redirect_back(fallback_location: super_admin_agent_bots_path)
|
||||
end
|
||||
|
||||
def scoped_resource
|
||||
resource_class.with_attached_avatar
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,17 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
||||
sha
|
||||
postgres_status
|
||||
redis_metrics
|
||||
chatwoot_edition
|
||||
end
|
||||
|
||||
def chatwoot_edition
|
||||
@metrics['Chatwoot edition'] = if ChatwootApp.enterprise?
|
||||
'Enterprise'
|
||||
elsif ChatwootApp.custom?
|
||||
'Custom'
|
||||
else
|
||||
'Community'
|
||||
end
|
||||
end
|
||||
|
||||
def chatwoot_version
|
||||
|
||||
@@ -45,6 +45,17 @@ class SuperAdmin::UsersController < SuperAdmin::ApplicationController
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
|
||||
def destroy_avatar
|
||||
avatar = requested_resource.avatar
|
||||
avatar.purge
|
||||
redirect_back(fallback_location: super_admin_users_path)
|
||||
end
|
||||
|
||||
def scoped_resource
|
||||
resource_class.with_attached_avatar
|
||||
end
|
||||
|
||||
def resource_params
|
||||
permitted_params = super
|
||||
permitted_params.delete(:password) if permitted_params[:password].blank?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class SwaggerController < ApplicationController
|
||||
def respond
|
||||
if Rails.env.development? || Rails.env.test?
|
||||
render inline: File.read(Rails.root.join('swagger', derived_path))
|
||||
render inline: Rails.root.join('swagger', derived_path).read
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ class WidgetsController < ActionController::Base
|
||||
source_id: @auth_token_params[:source_id]
|
||||
)
|
||||
|
||||
@contact = @contact_inbox ? @contact_inbox.contact : nil
|
||||
@contact = @contact_inbox&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
|
||||
@@ -10,6 +10,11 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
||||
ATTRIBUTE_TYPES = {
|
||||
access_token: Field::HasOne,
|
||||
avatar_url: AvatarField,
|
||||
avatar: Field::ActiveStorage.with_options(
|
||||
destroy_url: proc do |_namespace, _resource, attachment|
|
||||
[:avatar_super_admin_agent_bot, { attachment_id: attachment.id }]
|
||||
end
|
||||
),
|
||||
id: Field::Number,
|
||||
name: Field::String,
|
||||
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||
@@ -36,6 +41,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
avatar_url
|
||||
account
|
||||
name
|
||||
description
|
||||
@@ -47,6 +53,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
name
|
||||
avatar
|
||||
account
|
||||
description
|
||||
outgoing_url
|
||||
|
||||
@@ -11,6 +11,11 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
account_users: Field::HasMany,
|
||||
id: Field::Number,
|
||||
avatar_url: AvatarField,
|
||||
avatar: Field::ActiveStorage.with_options(
|
||||
destroy_url: proc do |_namespace, _resource, attachment|
|
||||
[:avatar_super_admin_user, { attachment_id: attachment.id }]
|
||||
end
|
||||
),
|
||||
provider: Field::String,
|
||||
uid: Field::String,
|
||||
password: Field::Password,
|
||||
@@ -69,6 +74,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
name
|
||||
avatar
|
||||
display_name
|
||||
email
|
||||
password
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
class AccountDrop < BaseDrop
|
||||
def name
|
||||
@obj.try(:name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,9 @@ class ConversationFinder
|
||||
SORT_OPTIONS = {
|
||||
latest: 'latest',
|
||||
sort_on_created_at: 'sort_on_created_at',
|
||||
last_user_message_at: 'last_user_message_at'
|
||||
last_user_message_at: 'last_user_message_at',
|
||||
sort_on_priority: 'sort_on_priority',
|
||||
sort_on_waiting_since: 'sort_on_waiting_since'
|
||||
}.with_indifferent_access
|
||||
|
||||
# assumptions
|
||||
@@ -53,9 +55,10 @@ class ConversationFinder
|
||||
|
||||
find_all_conversations
|
||||
filter_by_status unless params[:q]
|
||||
filter_by_team if @team
|
||||
filter_by_labels if params[:labels]
|
||||
filter_by_query if params[:q]
|
||||
filter_by_team
|
||||
filter_by_labels
|
||||
filter_by_query
|
||||
filter_by_source_id
|
||||
end
|
||||
|
||||
def set_inboxes
|
||||
@@ -100,12 +103,14 @@ class ConversationFinder
|
||||
when 'participating'
|
||||
@conversations = current_user.participating_conversations.where(account_id: current_account.id)
|
||||
when 'unattended'
|
||||
@conversations = @conversations.where(first_reply_created_at: nil)
|
||||
@conversations = @conversations.unattended
|
||||
end
|
||||
@conversations
|
||||
end
|
||||
|
||||
def filter_by_query
|
||||
return unless params[:q]
|
||||
|
||||
allowed_message_types = [Message.message_types[:incoming], Message.message_types[:outgoing]]
|
||||
@conversations = conversations.joins(:messages).where('messages.content ILIKE :search', search: "%#{params[:q]}%")
|
||||
.where(messages: { message_type: allowed_message_types }).includes(:messages)
|
||||
@@ -120,13 +125,24 @@ class ConversationFinder
|
||||
end
|
||||
|
||||
def filter_by_team
|
||||
return unless @team
|
||||
|
||||
@conversations = @conversations.where(team: @team)
|
||||
end
|
||||
|
||||
def filter_by_labels
|
||||
return unless params[:labels]
|
||||
|
||||
@conversations = @conversations.tagged_with(params[:labels], any: true)
|
||||
end
|
||||
|
||||
def filter_by_source_id
|
||||
return unless params[:source_id]
|
||||
|
||||
@conversations = @conversations.joins(:contact_inbox)
|
||||
@conversations = @conversations.where(contact_inboxes: { source_id: params[:source_id] })
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
[
|
||||
@conversations.assigned_to(current_user).count,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
module BillingHelper
|
||||
private
|
||||
|
||||
def default_plan?(account)
|
||||
installation_config = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')
|
||||
default_plan = installation_config&.value&.first
|
||||
|
||||
# Return false if not plans are configured, so that no checks are enforced
|
||||
return false if default_plan.blank?
|
||||
|
||||
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan['name']
|
||||
end
|
||||
|
||||
def conversations_this_month(account)
|
||||
account.conversations.where('created_at > ?', 30.days.ago).count
|
||||
end
|
||||
|
||||
def non_web_inboxes(account)
|
||||
account.inboxes.where.not(channel_type: Channel::WebWidget.to_s).count
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ module DateRangeHelper
|
||||
def range
|
||||
return if params[:since].blank? || params[:until].blank?
|
||||
|
||||
parse_date_time(params[:since])..parse_date_time(params[:until])
|
||||
parse_date_time(params[:since])...parse_date_time(params[:until])
|
||||
end
|
||||
|
||||
def parse_date_time(datetime)
|
||||
|
||||
@@ -10,4 +10,26 @@ module EmailHelper
|
||||
def normalize_email_with_plus_addressing(email)
|
||||
"#{email.split('@').first.split('+').first}@#{email.split('@').last}".downcase
|
||||
end
|
||||
|
||||
def parse_email_variables(conversation, email)
|
||||
case email
|
||||
when modified_liquid_content(email)
|
||||
template = Liquid::Template.parse(modified_liquid_content(email))
|
||||
template.render(message_drops(conversation))
|
||||
when URI::MailTo::EMAIL_REGEXP
|
||||
email
|
||||
end
|
||||
end
|
||||
|
||||
def modified_liquid_content(email)
|
||||
# This regex is used to match the code blocks in the content
|
||||
# We don't want to process liquid in code blocks
|
||||
email.gsub(/`(.*?)`/m, '{% raw %}`\\1`{% endraw %}')
|
||||
end
|
||||
|
||||
def message_drops(conversation)
|
||||
{
|
||||
'contact' => ContactDrop.new(conversation.contact)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,8 +7,6 @@ module MessageFormatHelper
|
||||
end
|
||||
|
||||
def render_message_content(message_content)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
CommonMarker.render_html(message_content).html_safe
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
ChatwootMarkdownRenderer.new(message_content).render_message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,9 @@ module ReportHelper
|
||||
end
|
||||
|
||||
def resolutions_count
|
||||
(get_grouped_values scope.conversations.where(account_id: account.id).resolved).count
|
||||
object_scope = scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
|
||||
conversations: { status: :resolved }).distinct
|
||||
(get_grouped_values object_scope).count
|
||||
end
|
||||
|
||||
def avg_first_response_time
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
v-if="!authUIFlags.isFetching"
|
||||
id="app"
|
||||
class="app-wrapper app-root"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView, dark: theme === 'dark' }"
|
||||
:dir="isRTLView ? 'rtl' : 'ltr'"
|
||||
>
|
||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
|
||||
<payment-pending-banner />
|
||||
<upgrade-banner />
|
||||
</template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
</transition>
|
||||
@@ -25,13 +30,17 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
|
||||
import LoadingState from './components/widgets/LoadingState.vue';
|
||||
import NetworkNotification from './components/NetworkNotification';
|
||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||
import UpgradeBanner from './components/app/UpgradeBanner.vue';
|
||||
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||
import vueActionCable from './helper/actionCable';
|
||||
import WootSnackbarBox from './components/SnackbarContainer';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
||||
import {
|
||||
registerSubscription,
|
||||
verifyServiceWorkerExistence,
|
||||
} from './helper/pushHelper';
|
||||
import { LOCAL_STORAGE_KEYS } from './constants/localStorage';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
@@ -41,7 +50,9 @@ export default {
|
||||
LoadingState,
|
||||
NetworkNotification,
|
||||
UpdateBanner,
|
||||
PaymentPendingBanner,
|
||||
WootSnackbarBox,
|
||||
UpgradeBanner,
|
||||
},
|
||||
|
||||
mixins: [rtlMixin],
|
||||
@@ -50,6 +61,7 @@ export default {
|
||||
return {
|
||||
showAddAccountModal: false,
|
||||
latestChatwootVersion: null,
|
||||
theme: 'light',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -59,6 +71,7 @@ export default {
|
||||
currentUser: 'getCurrentUser',
|
||||
globalConfig: 'globalConfig/get',
|
||||
authUIFlags: 'getAuthUIFlags',
|
||||
accountUIFlags: 'accounts/getUIFlags',
|
||||
currentAccountId: 'getCurrentAccountId',
|
||||
}),
|
||||
hasAccounts() {
|
||||
@@ -80,9 +93,34 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initializeColorTheme();
|
||||
this.listenToThemeChanges();
|
||||
this.setLocale(window.chatwootConfig.selectedLocale);
|
||||
},
|
||||
methods: {
|
||||
initializeColorTheme() {
|
||||
this.setColorTheme(
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
);
|
||||
},
|
||||
setColorTheme(isOSOnDarkMode) {
|
||||
const selectedColorScheme =
|
||||
LocalStorage.get(LOCAL_STORAGE_KEYS.COLOR_SCHEME) || 'light';
|
||||
if (
|
||||
(selectedColorScheme === 'auto' && isOSOnDarkMode) ||
|
||||
selectedColorScheme === 'dark'
|
||||
) {
|
||||
this.theme = 'dark';
|
||||
document.body.classList.add('dark');
|
||||
} else {
|
||||
this.theme = 'light ';
|
||||
document.body.classList.remove('dark');
|
||||
}
|
||||
},
|
||||
listenToThemeChanges() {
|
||||
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mql.onchange = e => this.setColorTheme(e.matches);
|
||||
},
|
||||
setLocale(locale) {
|
||||
this.$root.$i18n.locale = locale;
|
||||
},
|
||||
@@ -115,11 +153,6 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
@import './assets/scss/app';
|
||||
.update-banner {
|
||||
height: var(--space-larger);
|
||||
align-items: center;
|
||||
font-size: var(--font-size-small) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
||||
|
||||
@@ -18,6 +18,10 @@ class CacheEnabledApiClient extends ApiClient {
|
||||
return this.getFromCache();
|
||||
}
|
||||
|
||||
return this.getFromNetwork();
|
||||
}
|
||||
|
||||
getFromNetwork() {
|
||||
return axios.get(this.url);
|
||||
}
|
||||
|
||||
@@ -32,7 +36,12 @@ class CacheEnabledApiClient extends ApiClient {
|
||||
}
|
||||
|
||||
async getFromCache() {
|
||||
await this.dataManager.initDb();
|
||||
try {
|
||||
// IDB is not supported in Firefox private mode: https://bugzilla.mozilla.org/show_bug.cgi?id=781982
|
||||
await this.dataManager.initDb();
|
||||
} catch {
|
||||
return this.getFromNetwork();
|
||||
}
|
||||
|
||||
const { data } = await axios.get(
|
||||
`/api/v1/accounts/${this.accountIdFromRoute}/cache_keys`
|
||||
@@ -55,16 +64,22 @@ class CacheEnabledApiClient extends ApiClient {
|
||||
}
|
||||
|
||||
async refetchAndCommit(newKey = null) {
|
||||
await this.dataManager.initDb();
|
||||
const response = await axios.get(this.url);
|
||||
this.dataManager.replace({
|
||||
modelName: this.cacheModelName,
|
||||
data: this.extractDataFromResponse(response),
|
||||
});
|
||||
const response = await this.getFromNetwork();
|
||||
|
||||
await this.dataManager.setCacheKeys({
|
||||
[this.cacheModelName]: newKey,
|
||||
});
|
||||
try {
|
||||
await this.dataManager.initDb();
|
||||
|
||||
this.dataManager.replace({
|
||||
modelName: this.cacheModelName,
|
||||
data: this.extractDataFromResponse(response),
|
||||
});
|
||||
|
||||
await this.dataManager.setCacheKeys({
|
||||
[this.cacheModelName]: newKey,
|
||||
});
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -3,47 +3,11 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import endPoints from './endPoints';
|
||||
import {
|
||||
setAuthCredentials,
|
||||
clearCookiesOnLogout,
|
||||
deleteIndexedDBOnLogout,
|
||||
} from '../store/utils/api';
|
||||
|
||||
export default {
|
||||
login(creds) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('auth/sign_in', creds)
|
||||
.then(response => {
|
||||
setAuthCredentials(response);
|
||||
resolve(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
register(creds) {
|
||||
const urlData = endPoints('register');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, {
|
||||
account_name: creds.accountName.trim(),
|
||||
user_full_name: creds.fullName.trim(),
|
||||
email: creds.email,
|
||||
password: creds.password,
|
||||
h_captcha_client_response: creds.hCaptchaClientResponse,
|
||||
})
|
||||
.then(response => {
|
||||
setAuthCredentials(response);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
validityCheck() {
|
||||
const urlData = endPoints('validityCheck');
|
||||
return axios.get(urlData.url);
|
||||
@@ -73,45 +37,6 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
verifyPasswordToken({ confirmationToken }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('auth/confirmation', {
|
||||
confirmation_token: confirmationToken,
|
||||
})
|
||||
.then(response => {
|
||||
setAuthCredentials(response);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.put('auth/password', {
|
||||
reset_password_token: resetPasswordToken,
|
||||
password_confirmation: confirmPassword,
|
||||
password,
|
||||
})
|
||||
.then(response => {
|
||||
setAuthCredentials(response);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetPassword({ email }) {
|
||||
const urlData = endPoints('resetPassword');
|
||||
return axios.post(urlData.url, { email });
|
||||
},
|
||||
|
||||
profileUpdate({
|
||||
password,
|
||||
password_confirmation,
|
||||
|
||||
@@ -75,6 +75,10 @@ class ContactAPI extends ApiClient {
|
||||
destroyAvatar(contactId) {
|
||||
return axios.delete(`${this.url}/${contactId}/avatar`);
|
||||
}
|
||||
|
||||
exportContacts() {
|
||||
return axios.get(`${this.url}/export`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ContactAPI();
|
||||
|
||||
@@ -6,7 +6,7 @@ class CSATReportsAPI extends ApiClient {
|
||||
super('csat_survey_responses', { accountScoped: true });
|
||||
}
|
||||
|
||||
get({ page, from, to, user_ids } = {}) {
|
||||
get({ page, from, to, user_ids, inbox_id, team_id, rating } = {}) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
page,
|
||||
@@ -14,24 +14,31 @@ class CSATReportsAPI extends ApiClient {
|
||||
until: to,
|
||||
sort: '-created_at',
|
||||
user_ids,
|
||||
inbox_id,
|
||||
team_id,
|
||||
rating,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
download({ from, to, user_ids } = {}) {
|
||||
download({ from, to, user_ids, inbox_id, team_id, rating } = {}) {
|
||||
return axios.get(`${this.url}/download`, {
|
||||
params: {
|
||||
since: from,
|
||||
until: to,
|
||||
sort: '-created_at',
|
||||
user_ids,
|
||||
inbox_id,
|
||||
team_id,
|
||||
rating,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getMetrics({ from, to, user_ids } = {}) {
|
||||
getMetrics({ from, to, user_ids, inbox_id, team_id, rating } = {}) {
|
||||
// no ratings for metrics
|
||||
return axios.get(`${this.url}/metrics`, {
|
||||
params: { since: from, until: to, user_ids },
|
||||
params: { since: from, until: to, user_ids, inbox_id, team_id, rating },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ class EnterpriseAccountAPI extends ApiClient {
|
||||
subscription() {
|
||||
return axios.post(`${this.url}subscription`);
|
||||
}
|
||||
|
||||
getLimits() {
|
||||
return axios.get(`${this.url}limits`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new EnterpriseAccountAPI();
|
||||
|
||||
@@ -14,6 +14,7 @@ class ConversationApi extends ApiClient {
|
||||
labels,
|
||||
teamId,
|
||||
conversationType,
|
||||
sortBy,
|
||||
}) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
@@ -24,6 +25,7 @@ class ConversationApi extends ApiClient {
|
||||
page,
|
||||
labels,
|
||||
conversation_type: conversationType,
|
||||
sort_by: sortBy,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -52,6 +54,12 @@ class ConversationApi extends ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
togglePriority({ conversationId, priority }) {
|
||||
return axios.post(`${this.url}/${conversationId}/toggle_priority`, {
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
assignAgent({ conversationId, agentId }) {
|
||||
return axios.post(
|
||||
`${this.url}/${conversationId}/assignments?assignee_id=${agentId}`,
|
||||
@@ -119,6 +127,10 @@ class ConversationApi extends ApiClient {
|
||||
user_ids: userIds,
|
||||
});
|
||||
}
|
||||
|
||||
getAllAttachments(conversationId) {
|
||||
return axios.get(`${this.url}/${conversationId}/attachments`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConversationApi();
|
||||
|
||||
@@ -10,6 +10,7 @@ export const buildCreatePayload = ({
|
||||
files,
|
||||
ccEmails = '',
|
||||
bccEmails = '',
|
||||
toEmails = '',
|
||||
templateParams,
|
||||
}) => {
|
||||
let payload;
|
||||
@@ -25,6 +26,9 @@ export const buildCreatePayload = ({
|
||||
payload.append('echo_id', echoId);
|
||||
payload.append('cc_emails', ccEmails);
|
||||
payload.append('bcc_emails', bccEmails);
|
||||
if (toEmails) {
|
||||
payload.append('to_emails', toEmails);
|
||||
}
|
||||
} else {
|
||||
payload = {
|
||||
content: message,
|
||||
@@ -33,6 +37,7 @@ export const buildCreatePayload = ({
|
||||
content_attributes: contentAttributes,
|
||||
cc_emails: ccEmails,
|
||||
bcc_emails: bccEmails,
|
||||
to_emails: toEmails,
|
||||
template_params: templateParams,
|
||||
};
|
||||
}
|
||||
@@ -53,6 +58,7 @@ class MessageApi extends ApiClient {
|
||||
files,
|
||||
ccEmails = '',
|
||||
bccEmails = '',
|
||||
toEmails = '',
|
||||
templateParams,
|
||||
}) {
|
||||
return axios({
|
||||
@@ -66,6 +72,7 @@ class MessageApi extends ApiClient {
|
||||
files,
|
||||
ccEmails,
|
||||
bccEmails,
|
||||
toEmails,
|
||||
templateParams,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/* global axios */
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
/**
|
||||
* Represents the data object for a OpenAI hook.
|
||||
* @typedef {Object} ConversationMessageData
|
||||
* @property {string} [tone] - The tone of the message.
|
||||
* @property {string} [content] - The content of the message.
|
||||
* @property {string} [conversation_display_id] - The display ID of the conversation (optional).
|
||||
*/
|
||||
|
||||
/**
|
||||
* A client for the OpenAI API.
|
||||
* @extends ApiClient
|
||||
*/
|
||||
class OpenAIAPI extends ApiClient {
|
||||
/**
|
||||
* Creates a new OpenAIAPI instance.
|
||||
*/
|
||||
constructor() {
|
||||
super('integrations', { accountScoped: true });
|
||||
|
||||
/**
|
||||
* The conversation events supported by the API.
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.conversation_events = [
|
||||
'summarize',
|
||||
'reply_suggestion',
|
||||
'label_suggestion',
|
||||
];
|
||||
|
||||
/**
|
||||
* The message events supported by the API.
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.message_events = ['rephrase'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an event using the OpenAI API.
|
||||
* @param {Object} options - The options for the event.
|
||||
* @param {string} [options.type='rephrase'] - The type of event to process.
|
||||
* @param {string} [options.content] - The content of the event.
|
||||
* @param {string} [options.tone] - The tone of the event.
|
||||
* @param {string} [options.conversationId] - The ID of the conversation to process the event for.
|
||||
* @param {string} options.hookId - The ID of the hook to use for processing the event.
|
||||
* @returns {Promise} A promise that resolves with the result of the event processing.
|
||||
*/
|
||||
processEvent({ type = 'rephrase', content, tone, conversationId, hookId }) {
|
||||
/**
|
||||
* @type {ConversationMessageData}
|
||||
*/
|
||||
let data = {
|
||||
tone,
|
||||
content,
|
||||
};
|
||||
|
||||
if (this.conversation_events.includes(type)) {
|
||||
data = {
|
||||
conversation_display_id: conversationId,
|
||||
};
|
||||
}
|
||||
|
||||
return axios.post(`${this.url}/hooks/${hookId}/process_event`, {
|
||||
event: {
|
||||
name: type,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new OpenAIAPI();
|
||||
@@ -40,6 +40,7 @@ class ReportsAPI extends ApiClient {
|
||||
id,
|
||||
group_by: groupBy,
|
||||
business_hours: businessHours,
|
||||
timezone_offset: getTimeOffset(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,30 @@ class SearchAPI extends ApiClient {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
contacts({ q }) {
|
||||
return axios.get(`${this.url}/contacts`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
conversations({ q }) {
|
||||
return axios.get(`${this.url}/conversations`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
messages({ q }) {
|
||||
return axios.get(`${this.url}/messages`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SearchAPI();
|
||||
|
||||
@@ -210,5 +210,12 @@ describe('#ConversationAPI', () => {
|
||||
{ params: { page: payload.page } }
|
||||
);
|
||||
});
|
||||
|
||||
it('#getAllAttachments', () => {
|
||||
conversationAPI.getAllAttachments(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/1/attachments'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,9 +42,13 @@ describe('#Reports API', () => {
|
||||
'/api/v2/reports/summary',
|
||||
{
|
||||
params: {
|
||||
business_hours: undefined,
|
||||
group_by: undefined,
|
||||
id: undefined,
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
timezone_offset: -0,
|
||||
type: 'account',
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
.mx-datepicker {
|
||||
width: 100%;
|
||||
&.no-margin {
|
||||
.mx-input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker-range {
|
||||
width: 320px;
|
||||
&:not(.auto-width) {
|
||||
.mx-datepicker-range {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx-input {
|
||||
@@ -18,7 +26,7 @@
|
||||
border-radius: var(--border-radius-normal);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
height: 4.0rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.mx-input:disabled,
|
||||
@@ -27,3 +35,32 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker-inline {
|
||||
width: 100%;
|
||||
|
||||
.mx-calendar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cell.disabled {
|
||||
background-color: var(--s-25);
|
||||
color: var(--s-200);
|
||||
}
|
||||
|
||||
.mx-time-item.disabled {
|
||||
background-color: var(--s-25);
|
||||
}
|
||||
|
||||
.today {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.mx-datepicker-main {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.mx-time-header {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
select {
|
||||
height: 4.0rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -20,15 +20,6 @@ select {
|
||||
margin-left: var(--space-normal);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
background-color: var(--black-transparent);
|
||||
border-radius: $space-smaller;
|
||||
font-size: $font-size-mini;
|
||||
max-width: var(--space-giga);
|
||||
padding: $space-smaller $space-small;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 0;
|
||||
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
// Disable contrast warnings in Foundation.
|
||||
$contrast-warnings: false;
|
||||
|
||||
$global-font-size: 10px;
|
||||
$global-font-size: 16px;
|
||||
$global-width: 100%;
|
||||
$global-lineheight: 1.5;
|
||||
$foundation-palette: (primary: $color-woot,
|
||||
@@ -63,7 +63,7 @@ $black: #000;
|
||||
$white: #fff;
|
||||
$body-background: $white;
|
||||
$body-font-color: $color-body;
|
||||
$body-font-family: 'Inter',
|
||||
$body-font-family: 'PlusJakarta',
|
||||
-apple-system,
|
||||
system-ui,
|
||||
BlinkMacSystemFont,
|
||||
@@ -117,7 +117,7 @@ $header-font-style: normal;
|
||||
$font-family-monospace: $body-font-family;
|
||||
$header-color: $color-heading;
|
||||
$header-lineheight: 1.4;
|
||||
$header-margin-bottom: 0.5rem;
|
||||
$header-margin-bottom: 0.3125rem;
|
||||
$header-styles: (small: ("h1": ("font-size": 24),
|
||||
"h2": ("font-size": 20),
|
||||
"h3": ("font-size": 19),
|
||||
@@ -133,7 +133,7 @@ $header-styles: (small: ("h1": ("font-size": 24),
|
||||
$header-text-rendering: optimizeLegibility;
|
||||
$small-font-size: 80%;
|
||||
$header-small-font-color: $medium-gray;
|
||||
$paragraph-lineheight: 1.45;
|
||||
$paragraph-lineheight: 1.65;
|
||||
$paragraph-margin-bottom: var(--space-small);
|
||||
$paragraph-text-rendering: optimizeLegibility;
|
||||
$code-color: $black;
|
||||
@@ -153,11 +153,11 @@ $list-lineheight: $paragraph-lineheight;
|
||||
$list-margin-bottom: $paragraph-margin-bottom;
|
||||
$list-style-type: disc;
|
||||
$list-style-position: outside;
|
||||
$list-side-margin: 1.25rem;
|
||||
$list-nested-side-margin: 1.25rem;
|
||||
$defnlist-margin-bottom: 1rem;
|
||||
$list-side-margin: 0.78125rem;
|
||||
$list-nested-side-margin: 0.78125rem;
|
||||
$defnlist-margin-bottom: 0.6875rem;
|
||||
$defnlist-term-weight: $global-weight-bold;
|
||||
$defnlist-term-margin-bottom: 0.3rem;
|
||||
$defnlist-term-margin-bottom: 0.1875rem;
|
||||
$blockquote-color: $dark-gray;
|
||||
$blockquote-padding: rem-calc(9 20 0 19);
|
||||
$blockquote-border: 1px solid $medium-gray;
|
||||
@@ -179,9 +179,9 @@ $lead-lineheight: 1.6;
|
||||
$subheader-lineheight: 1.4;
|
||||
$subheader-color: $dark-gray;
|
||||
$subheader-font-weight: $global-weight-normal;
|
||||
$subheader-margin-top: 0.2rem;
|
||||
$subheader-margin-bottom: 0.5rem;
|
||||
$stat-font-size: 2.5rem;
|
||||
$subheader-margin-top: 0.125rem;
|
||||
$subheader-margin-bottom: 0.3125rem;
|
||||
$stat-font-size: 1.5625rem;
|
||||
|
||||
// 6. Abide
|
||||
// --------
|
||||
@@ -202,11 +202,11 @@ $accordion-plusminus: true;
|
||||
$accordion-title-font-size: rem-calc(12);
|
||||
$accordion-item-color: $primary-color;
|
||||
$accordion-item-background-hover: $light-gray;
|
||||
$accordion-item-padding: 1.25rem 1rem;
|
||||
$accordion-item-padding: 0.78125rem 0.625rem;
|
||||
$accordion-content-background: $white;
|
||||
$accordion-content-border: 1px solid $light-gray;
|
||||
$accordion-content-color: $body-font-color;
|
||||
$accordion-content-padding: 1rem;
|
||||
$accordion-content-padding: 0.625rem;
|
||||
|
||||
// 8. Accordion Menu
|
||||
// -----------------
|
||||
@@ -234,7 +234,7 @@ $breadcrumbs-item-font-size: rem-calc(11);
|
||||
$breadcrumbs-item-color: $primary-color;
|
||||
$breadcrumbs-item-color-current: $black;
|
||||
$breadcrumbs-item-color-disabled: $medium-gray;
|
||||
$breadcrumbs-item-margin: 0.75rem;
|
||||
$breadcrumbs-item-margin: 0.46875rem;
|
||||
$breadcrumbs-item-uppercase: true;
|
||||
$breadcrumbs-item-slash: true;
|
||||
|
||||
@@ -254,7 +254,7 @@ $button-sizes: (tiny: var(--font-size-micro),
|
||||
default: var(--font-size-small),
|
||||
large: var(--font-size-medium));
|
||||
$button-palette: $foundation-palette;
|
||||
$button-opacity-disabled: 0.25;
|
||||
$button-opacity-disabled: 0.4;
|
||||
$button-background-hover-lightness: -20%;
|
||||
$button-hollow-hover-lightness: -50%;
|
||||
$button-transition: background-color 0.25s ease-out,
|
||||
@@ -275,8 +275,8 @@ $buttongroup-radius-on-each: false;
|
||||
$callout-background: $white;
|
||||
$callout-background-fade: 85%;
|
||||
$callout-border: 1px solid rgba($black, 0.25);
|
||||
$callout-margin: 0 0 1rem 0;
|
||||
$callout-padding: 1rem;
|
||||
$callout-margin: 0 0 0.625rem 0;
|
||||
$callout-padding: 0.625rem;
|
||||
$callout-font-color: $body-font-color;
|
||||
$callout-font-color-alt: $body-background;
|
||||
$callout-radius: $global-radius;
|
||||
@@ -320,10 +320,10 @@ $drilldown-background: $white;
|
||||
// 17. Dropdown
|
||||
// ------------
|
||||
|
||||
$dropdown-padding: 1rem;
|
||||
$dropdown-padding: 0.625rem;
|
||||
$dropdown-background: $body-background;
|
||||
$dropdown-border: 1px solid $medium-gray;
|
||||
$dropdown-font-size: 1rem;
|
||||
$dropdown-font-size: 0.625rem;
|
||||
$dropdown-width: 300px;
|
||||
$dropdown-radius: $global-radius;
|
||||
$dropdown-sizes: (tiny: 100px,
|
||||
@@ -354,7 +354,7 @@ $helptext-font-style: italic;
|
||||
$input-prefix-color: $color-body;
|
||||
$input-prefix-background: var(--b-100);
|
||||
$input-prefix-border: 1px solid $color-border;
|
||||
$input-prefix-padding: 1rem;
|
||||
$input-prefix-padding: 0.625rem;
|
||||
$form-label-color: $color-body;
|
||||
$form-label-font-size: rem-calc(14);
|
||||
$form-label-font-weight: $font-weight-medium;
|
||||
@@ -406,14 +406,14 @@ $menu-margin-nested: $space-medium;
|
||||
$menu-item-padding: $space-slab;
|
||||
$menu-item-color-active: $white;
|
||||
$menu-item-background-active: $color-background;
|
||||
$menu-icon-spacing: 0.25rem;
|
||||
$menu-icon-spacing: 0.15625rem;
|
||||
$menu-item-background-hover: $light-gray;
|
||||
$menu-border: $light-gray;
|
||||
|
||||
// 23. Meter
|
||||
// ---------
|
||||
|
||||
$meter-height: 1rem;
|
||||
$meter-height: 0.625rem;
|
||||
$meter-radius: $global-radius;
|
||||
$meter-background: $medium-gray;
|
||||
$meter-fill-good: $success-color;
|
||||
@@ -423,11 +423,11 @@ $meter-fill-bad: $alert-color;
|
||||
// 24. Off-canvas
|
||||
// --------------
|
||||
|
||||
$offcanvas-sizes: (small: 23rem,
|
||||
medium: 23rem,
|
||||
$offcanvas-sizes: (small: 14.375,
|
||||
medium: 14.375,
|
||||
);
|
||||
$offcanvas-vertical-sizes: (small: 23rem,
|
||||
medium: 23rem,
|
||||
$offcanvas-vertical-sizes: (small: 14.375,
|
||||
medium: 14.375,
|
||||
);
|
||||
$offcanvas-background: $light-gray;
|
||||
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
|
||||
@@ -445,14 +445,14 @@ $maincontent-class: 'off-canvas-content';
|
||||
|
||||
$orbit-bullet-background: $medium-gray;
|
||||
$orbit-bullet-background-active: $dark-gray;
|
||||
$orbit-bullet-diameter: 1.2rem;
|
||||
$orbit-bullet-margin: 0.1rem;
|
||||
$orbit-bullet-margin-top: 0.8rem;
|
||||
$orbit-bullet-margin-bottom: 0.8rem;
|
||||
$orbit-bullet-diameter: 0.75rem;
|
||||
$orbit-bullet-margin: 0.0625rem;
|
||||
$orbit-bullet-margin-top: 0.5rem;
|
||||
$orbit-bullet-margin-bottom: 0.5rem;
|
||||
$orbit-caption-background: rgba($black, 0.5);
|
||||
$orbit-caption-padding: 1rem;
|
||||
$orbit-caption-padding: 0.625rem;
|
||||
$orbit-control-background-hover: rgba($black, 0.5);
|
||||
$orbit-control-padding: 1rem;
|
||||
$orbit-control-padding: 0.625rem;
|
||||
$orbit-control-zindex: 10;
|
||||
|
||||
// 26. Pagination
|
||||
@@ -476,7 +476,7 @@ $pagination-arrows: true;
|
||||
// 27. Progress Bar
|
||||
// ----------------
|
||||
|
||||
$progress-height: 1rem;
|
||||
$progress-height: 0.625rem;
|
||||
$progress-background: $medium-gray;
|
||||
$progress-margin-bottom: $global-margin;
|
||||
$progress-meter-background: $primary-color;
|
||||
@@ -504,13 +504,13 @@ $reveal-overlay-background: rgba($black, 0.45);
|
||||
// 30. Slider
|
||||
// ----------
|
||||
|
||||
$slider-width-vertical: 0.5rem;
|
||||
$slider-width-vertical: 0.3125rem;
|
||||
$slider-transition: all 0.2s ease-in-out;
|
||||
$slider-height: 0.5rem;
|
||||
$slider-height: 0.3125rem;
|
||||
$slider-background: $light-gray;
|
||||
$slider-fill-background: $medium-gray;
|
||||
$slider-handle-height: 1.4rem;
|
||||
$slider-handle-width: 1.4rem;
|
||||
$slider-handle-height: 0.875rem;
|
||||
$slider-handle-width: 0.875rem;
|
||||
$slider-handle-background: $primary-color;
|
||||
$slider-opacity-disabled: 0.25;
|
||||
$slider-radius: $global-radius;
|
||||
@@ -569,7 +569,7 @@ $tab-expand-max: 6;
|
||||
$tab-content-background: transparent;
|
||||
$tab-content-border: transparent;
|
||||
$tab-content-color: foreground($tab-background, $primary-color);
|
||||
$tab-content-padding: 1rem;
|
||||
$tab-content-padding: 0.625rem;
|
||||
|
||||
// 34. Thumbnail
|
||||
// -------------
|
||||
@@ -586,11 +586,11 @@ $thumbnail-radius: $global-radius;
|
||||
|
||||
$titlebar-background: $black;
|
||||
$titlebar-color: $white;
|
||||
$titlebar-padding: 0.5rem;
|
||||
$titlebar-padding: 0.3125rem;
|
||||
$titlebar-text-font-weight: bold;
|
||||
$titlebar-icon-color: $white;
|
||||
$titlebar-icon-color-hover: $medium-gray;
|
||||
$titlebar-icon-spacing: 0.25rem;
|
||||
$titlebar-icon-spacing: 0.15625rem;
|
||||
|
||||
// 36. Tooltip
|
||||
// -----------
|
||||
@@ -599,19 +599,19 @@ $has-tip-font-weight: $global-weight-bold;
|
||||
$has-tip-border-bottom: dotted 1px $dark-gray;
|
||||
$tooltip-background-color: $black;
|
||||
$tooltip-color: $white;
|
||||
$tooltip-padding: 0.75rem;
|
||||
$tooltip-padding: 0.46875rem;
|
||||
$tooltip-font-size: $font-size-mini;
|
||||
$tooltip-pip-width: 0.75rem;
|
||||
$tooltip-pip-width: 0.46875rem;
|
||||
$tooltip-pip-height: $tooltip-pip-width * 0.866;
|
||||
$tooltip-radius: $global-radius;
|
||||
|
||||
// 37. Top Bar
|
||||
// -----------
|
||||
|
||||
$topbar-padding: 0.5rem;
|
||||
$topbar-padding: 0.3125;
|
||||
$topbar-background: $light-gray;
|
||||
$topbar-submenu-background: $topbar-background;
|
||||
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
|
||||
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
|
||||
$topbar-input-width: 200px;
|
||||
$topbar-unstack-breakpoint: medium;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.bg-light {
|
||||
@include background-light;
|
||||
@apply bg-slate-25 dark:bg-slate-800;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
|
||||
@@ -22,43 +22,43 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@mixin border-normal() {
|
||||
border: 1px solid $color-border;
|
||||
@apply border border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-normal-left() {
|
||||
border-left: 1px solid $color-border;
|
||||
@apply border-l border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-normal-top() {
|
||||
border-top: 1px solid $color-border;
|
||||
@apply border-t border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-normal-right() {
|
||||
border-right: 1px solid $color-border;
|
||||
@apply border-r border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-normal-bottom() {
|
||||
border-bottom: 1px solid $color-border;
|
||||
@apply border-b border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-light() {
|
||||
border: 1px solid $color-border-light;
|
||||
@apply border border-slate-25 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-light-left() {
|
||||
border-left: 1px solid $color-border-light;
|
||||
@apply border-l border-slate-25 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-light-top() {
|
||||
border-top: 1px solid $color-border-light;
|
||||
@apply border-t border-slate-25 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-light-right() {
|
||||
border-right: 1px solid $color-border-light;
|
||||
@apply border-r border-slate-25 dark:border-slate-700;
|
||||
}
|
||||
|
||||
@mixin border-light-bottom() {
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
@apply border-b border-slate-25 dark:border-slate-700;
|
||||
}
|
||||
|
||||
// background
|
||||
@@ -67,11 +67,11 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@mixin background-light() {
|
||||
background: $color-background-light;
|
||||
@apply bg-slate-50 dark:bg-slate-800;
|
||||
}
|
||||
|
||||
@mixin background-white() {
|
||||
background: $color-white;
|
||||
@apply bg-white dark:bg-slate-900;
|
||||
}
|
||||
|
||||
// input form
|
||||
@@ -237,8 +237,8 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@mixin three-column-grid($column-one-width: 25.6rem,
|
||||
$column-three-width: 25.6rem) {
|
||||
@mixin three-column-grid($column-one-width: 16rem,
|
||||
$column-three-width: 16rem) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
// Primary sidebar
|
||||
.primary--sidebar {
|
||||
border-left: 1px solid var(--s-50);
|
||||
border-right: 0;
|
||||
|
||||
.options-menu.dropdown-pane {
|
||||
right: var(--space-smaller);
|
||||
|
||||
@@ -20,63 +17,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary sidebar
|
||||
.secondary-sidebar {
|
||||
.secondary-menu {
|
||||
border-left: 1px solid var(--s-50);
|
||||
border-right: 0;
|
||||
|
||||
.nested.vertical.menu {
|
||||
.badge--icon {
|
||||
margin-left: var(--space-smaller);
|
||||
margin-right: unset;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-menu--icon {
|
||||
margin-left: var(--space-smaller);
|
||||
margin-right: unset;
|
||||
}
|
||||
|
||||
.account-context--group .account-context--switch-group {
|
||||
--overlay-shadow: linear-gradient(
|
||||
to left,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 1) 50%
|
||||
);
|
||||
background-image: var(--overlay-shadow);
|
||||
}
|
||||
|
||||
// Help center sidebar
|
||||
.sidebar-header--wrap .header-title--wrap {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Woot button
|
||||
.button {
|
||||
.icon--emoji + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
|
||||
.icon--font + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
|
||||
.icon + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
// Settings header
|
||||
.settings-header {
|
||||
.header--icon {
|
||||
@@ -85,7 +25,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.settings.back-button {
|
||||
.header-section.back-button {
|
||||
direction: initial;
|
||||
margin-left: var(--space-normal);
|
||||
margin-right: var(--space-smaller);
|
||||
@@ -261,6 +201,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Basic filter dropdown
|
||||
.basic-filter {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
// Card label
|
||||
.label-container {
|
||||
.label {
|
||||
@@ -433,7 +379,7 @@
|
||||
}
|
||||
|
||||
span {
|
||||
--minus-space-one-point-five: -1.5rem;
|
||||
--minus-space-one-point-five: -0.9375rem;
|
||||
|
||||
&.active {
|
||||
transform: translate(
|
||||
|
||||
@@ -29,4 +29,5 @@ a {
|
||||
|
||||
p {
|
||||
font-size: $font-size-small;
|
||||
word-spacing: .12em;
|
||||
}
|
||||
|
||||
@@ -43,17 +43,13 @@
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid var(--color-border);
|
||||
@apply border-r border-slate-50 dark:border-slate-700;
|
||||
}
|
||||
|
||||
.border-left {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
// Font sizes
|
||||
$font-size-nano: 0.8rem;
|
||||
$font-size-micro: 1.0rem;
|
||||
$font-size-mini: 1.2rem;
|
||||
$font-size-small: 1.4rem;
|
||||
$font-size-default: 1.6rem;
|
||||
$font-size-medium: 1.8rem;
|
||||
$font-size-large: 2.2rem;
|
||||
$font-size-big: 2.4rem;
|
||||
$font-size-bigger: 3.0rem;
|
||||
$font-size-mega: 3.4rem;
|
||||
$font-size-giga: 4.0rem;
|
||||
$font-size-nano: 0.5rem;
|
||||
$font-size-micro: 0.675rem;
|
||||
$font-size-mini: 0.75rem;
|
||||
$font-size-small: 0.875rem;
|
||||
$font-size-default: 1rem;
|
||||
$font-size-medium: 1.125rem;
|
||||
$font-size-large: 1.375rem;
|
||||
$font-size-big: 1.5rem;
|
||||
$font-size-bigger: 1.75rem;
|
||||
$font-size-mega: 2.125rem;
|
||||
$font-size-giga: 2.5rem;
|
||||
|
||||
// spaces
|
||||
$zero: 0;
|
||||
$space-micro: 0.2rem;
|
||||
$space-smaller: 0.4rem;
|
||||
$space-small: 0.8rem;
|
||||
$space-one: 1rem;
|
||||
$space-slab: 1.2rem;
|
||||
$space-normal: 1.6rem;
|
||||
$space-two: 2.0rem;
|
||||
$space-medium: 2.4rem;
|
||||
$space-large: 3.2rem;
|
||||
$space-larger: 4.8rem;
|
||||
$space-jumbo: 6.4rem;
|
||||
$space-mega: 10.0rem;
|
||||
$space-micro: 0.125rem;
|
||||
$space-smaller: 0.25rem;
|
||||
$space-small: 0.5rem;
|
||||
$space-one: 0.675rem;
|
||||
$space-slab: 0.75rem;
|
||||
$space-normal: 1rem;
|
||||
$space-two: 1.25rem;
|
||||
$space-medium: 1.5rem;
|
||||
$space-large: 2rem;
|
||||
$space-larger: 3rem;
|
||||
$space-jumbo: 4rem;
|
||||
$space-mega: 6.25rem;
|
||||
|
||||
// font-weight
|
||||
$font-weight-feather: 100;
|
||||
@@ -35,8 +35,8 @@ $font-weight-bold: 600;
|
||||
$font-weight-black: 700;
|
||||
|
||||
//Navbar
|
||||
$nav-bar-width: 23rem;
|
||||
$header-height: 5.6rem;
|
||||
$nav-bar-width: 14.375rem;
|
||||
$header-height: 3.5rem;
|
||||
|
||||
$woot-logo-padding: $space-large $space-two;
|
||||
|
||||
@@ -71,20 +71,20 @@ $color-primary-light: #c7e3ff;
|
||||
$color-primary-dark: darken($color-woot, 20%);
|
||||
|
||||
// Thumbnail
|
||||
$thumbnail-radius: 4rem;
|
||||
$thumbnail-radius: 2.5rem;
|
||||
|
||||
// chat-header
|
||||
$conv-header-height: 4rem;
|
||||
$conv-header-height: 2.5rem;
|
||||
|
||||
// Inbox List
|
||||
|
||||
$inbox-thumb-size: 4.8rem;
|
||||
$inbox-thumb-size: 3rem;
|
||||
|
||||
|
||||
// Spinner
|
||||
$spinkit-spinner-color: $color-white !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
|
||||
$spinkit-size: 1.6rem !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1rem !default;
|
||||
$spinkit-size: 1rem !default;
|
||||
|
||||
// Snackbar default
|
||||
$woot-snackbar-bg: #323232;
|
||||
@@ -101,5 +101,5 @@ $ionicons-font-path: '~ionicons/fonts';
|
||||
$transition-ease-in: all 0.250s ease-in;
|
||||
|
||||
:root {
|
||||
--dashboard-app-tabs-height: 3.9rem;
|
||||
--dashboard-app-tabs-height: 2.4375rem;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
@import 'shared/assets/fonts/inter';
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
@import 'shared/assets/fonts/plus-jakarta';
|
||||
@import 'shared/assets/stylesheets/animations';
|
||||
@import 'shared/assets/stylesheets/colors';
|
||||
@import 'shared/assets/stylesheets/spacing';
|
||||
@@ -67,3 +71,8 @@
|
||||
@import 'plugins/dropdown';
|
||||
@import '~shared/assets/stylesheets/ionicons';
|
||||
@import 'utility-helpers';
|
||||
|
||||
|
||||
.tooltip {
|
||||
@apply bg-slate-900 text-white py-1 px-2 z-40 text-xs rounded-md dark:bg-slate-200 dark:text-slate-900;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: var(--space-normal);
|
||||
&:not(.no-margin) {
|
||||
margin-bottom: var(--space-normal);
|
||||
}
|
||||
|
||||
&.multiselect--disabled {
|
||||
opacity: 0.8;
|
||||
@@ -21,12 +23,12 @@
|
||||
|
||||
.multiselect--active {
|
||||
>.multiselect__tags {
|
||||
border-color: $color-woot;
|
||||
border-color: var(--w-500);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__select {
|
||||
min-height: 4.6rem;
|
||||
min-height: 2.875rem;
|
||||
padding: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@@ -75,16 +77,13 @@
|
||||
}
|
||||
|
||||
&.multiselect__option--selected {
|
||||
background: var(--w-400);
|
||||
color: var(--white);
|
||||
background: var(--w-75);
|
||||
|
||||
&.multiselect__option--highlight:hover {
|
||||
background: var(--w-600);
|
||||
color: var(--white);
|
||||
background: var(--w-75);
|
||||
|
||||
&::after {
|
||||
background: transparent;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
&::after:hover {
|
||||
@@ -99,7 +98,7 @@
|
||||
border: 1px solid var(--s-200);
|
||||
border-color: var(--s-200);
|
||||
margin: 0;
|
||||
min-height: 4.4rem;
|
||||
min-height: 2.75rem;
|
||||
padding-top: $zero;
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@
|
||||
.multiselect__input {
|
||||
@include ghost-input;
|
||||
font-size: $font-size-small;
|
||||
height: 4.4rem;
|
||||
height: 2.75rem;
|
||||
margin-bottom: $zero;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -169,7 +168,7 @@
|
||||
}
|
||||
|
||||
.multiselect-wrap--small {
|
||||
$multiselect-height: 4.0rem;
|
||||
$multiselect-height: 2.5rem;
|
||||
|
||||
.multiselect__tags,
|
||||
.multiselect__input {
|
||||
@@ -196,6 +195,7 @@
|
||||
display: flex;
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0;
|
||||
max-height: 2.375rem;
|
||||
padding: var(--space-smaller) var(--space-micro);
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
}
|
||||
|
||||
.multiselect-wrap--medium {
|
||||
$multiselect-height: 4.8rem;
|
||||
$multiselect-height: 3rem;
|
||||
|
||||
.multiselect__tags,
|
||||
.multiselect__input {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import 'shared/assets/fonts/inter';
|
||||
@import 'shared/assets/fonts/plus-jakarta';
|
||||
@import 'shared/assets/stylesheets/animations';
|
||||
@import 'shared/assets/stylesheets/colors';
|
||||
@import 'shared/assets/stylesheets/spacing';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
@import 'shared/assets/fonts/inter';
|
||||
@import 'shared/assets/fonts/plus-jakarta';
|
||||
@import '../variables';
|
||||
@import '~shared/assets/stylesheets/ionicons';
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
|
||||
// Conversation header - Light BG
|
||||
.settings-header {
|
||||
@include background-white;
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
border-bottom: 1px solid var(--s-50);
|
||||
height: $header-height;
|
||||
min-height: $header-height;
|
||||
padding: $space-small $space-normal;
|
||||
@@ -27,7 +25,7 @@
|
||||
|
||||
.wizard-box {
|
||||
.item {
|
||||
@include background-light;
|
||||
// @include background-light;
|
||||
|
||||
cursor: pointer;
|
||||
padding: $space-normal $space-normal $space-normal $space-medium;
|
||||
@@ -128,8 +126,8 @@
|
||||
}
|
||||
|
||||
.wizard-body {
|
||||
@include background-white;
|
||||
@include border-light;
|
||||
@include background-white;
|
||||
@include full-height();
|
||||
padding: $space-medium;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user