Compare commits
1 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98bff6d8f6 |
@@ -5,30 +5,23 @@ orbs:
|
|||||||
aws-s3: circleci/aws-s3@4.0.0
|
aws-s3: circleci/aws-s3@4.0.0
|
||||||
aws-cli: circleci/aws-cli@4.0
|
aws-cli: circleci/aws-cli@4.0
|
||||||
eb: circleci/aws-elastic-beanstalk@2.0.1
|
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||||
jira: circleci/jira@2.1.0
|
|
||||||
jobs:
|
jobs:
|
||||||
imex-api-deploy:
|
imex-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 22 running on 64bit Amazon Linux 2"
|
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
- jira/notify:
|
|
||||||
environment: Production (ImEX) - API
|
|
||||||
environment_type: production
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
|
|
||||||
imex-hasura-migrate:
|
imex-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -40,19 +33,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
npm install hasura-cli -g
|
||||||
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
- jira/notify:
|
|
||||||
environment: Production (ImEX) - Hasura
|
|
||||||
environment_type: production
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
imex-app-build:
|
imex-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
steps:
|
steps:
|
||||||
@@ -74,10 +62,9 @@ jobs:
|
|||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
|
|
||||||
imex-app-beta-build:
|
imex-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -99,12 +86,6 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://imex-online-beta/"
|
to: "s3://imex-online-beta/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify:
|
|
||||||
environment: Production (ImEX) - Front End
|
|
||||||
environment_type: production
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
|
|
||||||
rome-api-deploy:
|
rome-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
@@ -114,19 +95,14 @@ jobs:
|
|||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init romeonline-productionapi -r us-east-2 -p "Node.js 22 on 64bit Amazon Linux 2"
|
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
- jira/notify:
|
|
||||||
environment: Production (Rome) - API
|
|
||||||
environment_type: production
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
rome-hasura-migrate:
|
rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -138,19 +114,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
npm install hasura-cli -g
|
||||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
- jira/notify:
|
|
||||||
environment: Production (Rome) - Hasura
|
|
||||||
environment_type: production
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
rome-app-build:
|
rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
|
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -172,16 +143,35 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://rome-online-production/"
|
to: "s3://rome-online-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify:
|
|
||||||
environment: Production (Rome) - Front End
|
promanager-app-build:
|
||||||
environment_type: production
|
docker:
|
||||||
pipeline_id: << pipeline.id >>
|
- image: cimg/node:18.18.2
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build:production:promanager
|
||||||
|
|
||||||
|
- aws-cli/setup:
|
||||||
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
|
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
|
||||||
|
region: AWS_REGION
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: dist
|
||||||
|
to: "s3://promanager-production/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
test-rome-hasura-migrate:
|
test-rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -193,22 +183,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
npm install hasura-cli -g
|
||||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
sleep 5
|
|
||||||
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
sleep 10
|
|
||||||
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
- jira/notify:
|
|
||||||
environment: Test (Rome) - Hasura
|
|
||||||
environment_type: testing
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
|
|
||||||
test-rome-app-build:
|
test-rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
|
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -230,16 +212,35 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://rome-online-test/"
|
to: "s3://rome-online-test/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify:
|
|
||||||
environment: Test (Rome) - Front End
|
test-promanager-app-build:
|
||||||
environment_type: testing
|
docker:
|
||||||
pipeline_id: << pipeline.id >>
|
- image: cimg/node:18.18.2
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build:test:promanager
|
||||||
|
|
||||||
|
- aws-cli/setup:
|
||||||
|
aws_access_key_id: AWS_ACCESS_KEY_ID
|
||||||
|
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
|
||||||
|
region: AWS_REGION
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: dist
|
||||||
|
to: "s3://promanager-testing/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -251,22 +252,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
npm install hasura-cli -g
|
||||||
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
sleep 15
|
|
||||||
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
sleep 30
|
|
||||||
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
- jira/notify:
|
|
||||||
environment: Test (ImEX) - Hasura
|
|
||||||
environment_type: testing
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
|
|
||||||
imex-test-app-build:
|
imex-test-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -286,7 +279,7 @@ jobs:
|
|||||||
|
|
||||||
imex-test-app-beta-build:
|
imex-test-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:22.13.1
|
- image: cimg/node:18.18.2
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -309,12 +302,7 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://imex-online-test-beta/"
|
to: "s3://imex-online-test-beta/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify:
|
|
||||||
environment: Test (ImEX) - Front End
|
|
||||||
environment_type: testing
|
|
||||||
pipeline_id: << pipeline.id >>
|
|
||||||
job_type: deployment
|
|
||||||
pipeline_number: << pipeline.number >>
|
|
||||||
|
|
||||||
admin-app-build:
|
admin-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -365,7 +353,7 @@ workflows:
|
|||||||
secret: ${HASURA_PROD_SECRET}
|
secret: ${HASURA_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master-AIO
|
only: master
|
||||||
- rome-api-deploy:
|
- rome-api-deploy:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
@@ -375,7 +363,7 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only: master-AIO
|
only: master-AIO
|
||||||
- rome-hasura-migrate:
|
- rome-hasura-migrate:
|
||||||
secret: ${HASURA_ROME_PROD_SECRET}
|
secret: ${HASURA_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master-AIO
|
only: master-AIO
|
||||||
@@ -396,6 +384,14 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: test-AIO
|
only: test-AIO
|
||||||
|
- test-promanager-app-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: test-AIO
|
||||||
|
- promanager-app-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master-AIO
|
||||||
- test-rome-hasura-migrate:
|
- test-rome-hasura-migrate:
|
||||||
secret: ${HASURA_ROME_TEST_SECRET}
|
secret: ${HASURA_ROME_TEST_SECRET}
|
||||||
filters:
|
filters:
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Directories to exclude
|
|
||||||
.circleci
|
|
||||||
.idea
|
|
||||||
.platform
|
|
||||||
.vscode
|
|
||||||
_reference
|
|
||||||
client
|
|
||||||
redis/dockerdata
|
|
||||||
hasura
|
|
||||||
node_modules
|
|
||||||
# Files to exclude
|
|
||||||
.ebignore
|
|
||||||
.editorconfig
|
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
|
||||||
.prettierrc.js
|
|
||||||
Dockerfile
|
|
||||||
README.MD
|
|
||||||
bodyshop_translations.babel
|
|
||||||
docker-compose.yml
|
|
||||||
ecosystem.config.js
|
|
||||||
|
|
||||||
# Optional: Exclude logs and temporary files
|
|
||||||
*.log
|
|
||||||
80
.gitattributes
vendored
80
.gitattributes
vendored
@@ -1,80 +0,0 @@
|
|||||||
# Ensure all text files use LF for line endings
|
|
||||||
* text eol=lf
|
|
||||||
|
|
||||||
# Binary files should not be modified by Git
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.ico binary
|
|
||||||
*.webp binary
|
|
||||||
*.svg binary
|
|
||||||
|
|
||||||
# Fonts
|
|
||||||
*.woff binary
|
|
||||||
*.woff2 binary
|
|
||||||
*.ttf binary
|
|
||||||
*.otf binary
|
|
||||||
*.eot binary
|
|
||||||
|
|
||||||
# Videos
|
|
||||||
*.mp4 binary
|
|
||||||
*.mov binary
|
|
||||||
*.avi binary
|
|
||||||
*.mkv binary
|
|
||||||
*.webm binary
|
|
||||||
|
|
||||||
# Audio
|
|
||||||
*.mp3 binary
|
|
||||||
*.wav binary
|
|
||||||
*.ogg binary
|
|
||||||
*.flac binary
|
|
||||||
|
|
||||||
# Archives and compressed files
|
|
||||||
*.zip binary
|
|
||||||
*.gz binary
|
|
||||||
*.tar binary
|
|
||||||
*.7z binary
|
|
||||||
*.rar binary
|
|
||||||
|
|
||||||
# PDF and documents
|
|
||||||
*.pdf binary
|
|
||||||
*.doc binary
|
|
||||||
*.docx binary
|
|
||||||
*.xls binary
|
|
||||||
*.xlsx binary
|
|
||||||
*.ppt binary
|
|
||||||
*.pptx binary
|
|
||||||
|
|
||||||
# Exclude JSON and other data files from text processing, if necessary
|
|
||||||
*.json text
|
|
||||||
*.xml text
|
|
||||||
*.csv text
|
|
||||||
|
|
||||||
# Scripts and code files should maintain LF endings
|
|
||||||
*.js text eol=lf
|
|
||||||
*.jsx text eol=lf
|
|
||||||
*.ts text eol=lf
|
|
||||||
*.tsx text eol=lf
|
|
||||||
*.css text eol=lf
|
|
||||||
*.scss text eol=lf
|
|
||||||
*.html text eol=lf
|
|
||||||
*.yml text eol=lf
|
|
||||||
*.yaml text eol=lf
|
|
||||||
*.md text eol=lf
|
|
||||||
*.sh text eol=lf
|
|
||||||
*.py text eol=lf
|
|
||||||
*.rb text eol=lf
|
|
||||||
*.java text eol=lf
|
|
||||||
*.php text eol=lf
|
|
||||||
|
|
||||||
# Git configuration files
|
|
||||||
.gitattributes text eol=lf
|
|
||||||
.gitignore text eol=lf
|
|
||||||
*.gitattributes text eol=lf
|
|
||||||
|
|
||||||
# Exclude some other potential binary files
|
|
||||||
*.db binary
|
|
||||||
*.sqlite binary
|
|
||||||
*.exe binary
|
|
||||||
*.dll binary
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Install required packages
|
|
||||||
dnf install -y fontconfig freetype
|
|
||||||
|
|
||||||
# Move to the /tmp directory for temporary download and extraction
|
|
||||||
cd /tmp
|
|
||||||
|
|
||||||
# Download the Montserrat font zip file
|
|
||||||
wget https://images.imex.online/fonts/montserrat.zip -O montserrat.zip
|
|
||||||
|
|
||||||
# Unzip the downloaded font file
|
|
||||||
unzip montserrat.zip -d montserrat
|
|
||||||
|
|
||||||
# Move the font files to the system fonts directory
|
|
||||||
mv montserrat/montserrat/*.ttf /usr/share/fonts
|
|
||||||
|
|
||||||
# Rebuild the font cache
|
|
||||||
fc-cache -fv
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm -rf /tmp/montserrat /tmp/montserrat.zip
|
|
||||||
|
|
||||||
echo "Montserrat fonts installed and cached successfully."
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
DD_API_KEY=58d91898a70c6fd659f6eea768a57976 DD_SITE="us3.datadoghq.com" bash -c "$(curl -L https://install.datadoghq.com/scripts/install_script_agent7.sh)"
|
|
||||||
|
|
||||||
echo "Datadog agent installed."
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
client_max_body_size 50M;
|
client_max_body_size 50M;
|
||||||
client_body_buffer_size 5M;
|
|
||||||
|
|||||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -14,21 +14,6 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "http://localhost:3000",
|
"url": "http://localhost:3000",
|
||||||
"webRoot": "${workspaceRoot}/client/src"
|
"webRoot": "${workspaceRoot}/client/src"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Attach to Node.js in Docker",
|
|
||||||
"type": "node",
|
|
||||||
"request": "attach",
|
|
||||||
"address": "localhost",
|
|
||||||
"port": 9229,
|
|
||||||
"localRoot": "${workspaceFolder}",
|
|
||||||
"remoteRoot": "/app",
|
|
||||||
"protocol": "inspector",
|
|
||||||
"restart": true,
|
|
||||||
"sourceMaps": true,
|
|
||||||
"skipFiles": [
|
|
||||||
"<node_internals>/**"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
33
.vscode/settings.json
vendored
33
.vscode/settings.json
vendored
@@ -8,36 +8,5 @@
|
|||||||
"pattern": "**/IMEX.xml",
|
"pattern": "**/IMEX.xml",
|
||||||
"systemId": "logs/IMEX.xsd"
|
"systemId": "logs/IMEX.xsd"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"cSpell.words": [
|
|
||||||
"antd",
|
|
||||||
"appointmentconfirmation",
|
|
||||||
"appt",
|
|
||||||
"autohouse",
|
|
||||||
"autohouseid",
|
|
||||||
"billlines",
|
|
||||||
"bodyshop",
|
|
||||||
"bodyshopid",
|
|
||||||
"bodyshops",
|
|
||||||
"CIECA",
|
|
||||||
"claimscorp",
|
|
||||||
"claimscorpid",
|
|
||||||
"Dinero",
|
|
||||||
"driveable",
|
|
||||||
"IMEX",
|
|
||||||
"imexshopid",
|
|
||||||
"jobid",
|
|
||||||
"joblines",
|
|
||||||
"Kaizen",
|
|
||||||
"labhrs",
|
|
||||||
"larhrs",
|
|
||||||
"mixdata",
|
|
||||||
"ownr",
|
|
||||||
"promanager",
|
|
||||||
"shopname",
|
|
||||||
"smartscheduling",
|
|
||||||
"timetickets",
|
|
||||||
"touchtime"
|
|
||||||
],
|
|
||||||
"eslint.workingDirectories": ["./", "./client"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
59
Dockerfile
59
Dockerfile
@@ -1,59 +0,0 @@
|
|||||||
# Use Amazon Linux 2023 as the base image
|
|
||||||
FROM amazonlinux:2023
|
|
||||||
|
|
||||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
|
||||||
RUN dnf install -y git \
|
|
||||||
&& curl -sL https://rpm.nodesource.com/setup_22.x | bash - \
|
|
||||||
&& dnf install -y nodejs \
|
|
||||||
&& dnf clean all
|
|
||||||
|
|
||||||
# Install dependencies required by node-canvas
|
|
||||||
RUN dnf install -y \
|
|
||||||
gcc \
|
|
||||||
gcc-c++ \
|
|
||||||
cairo-devel \
|
|
||||||
pango-devel \
|
|
||||||
libjpeg-turbo-devel \
|
|
||||||
giflib-devel \
|
|
||||||
libpng-devel \
|
|
||||||
make \
|
|
||||||
python3 \
|
|
||||||
fontconfig \
|
|
||||||
freetype \
|
|
||||||
python3-pip \
|
|
||||||
wget \
|
|
||||||
unzip \
|
|
||||||
&& dnf clean all
|
|
||||||
|
|
||||||
# Install Montserrat fonts
|
|
||||||
RUN cd /tmp \
|
|
||||||
&& wget https://images.imex.online/fonts/montserrat.zip -O montserrat.zip \
|
|
||||||
&& unzip montserrat.zip -d montserrat \
|
|
||||||
&& mv montserrat/montserrat/*.ttf /usr/share/fonts \
|
|
||||||
&& fc-cache -fv \
|
|
||||||
&& rm -rf /tmp/montserrat /tmp/montserrat.zip \
|
|
||||||
&& echo "Montserrat fonts installed and cached successfully."
|
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# This is because our test route uses a git commit hash
|
|
||||||
RUN git config --global --add safe.directory /app
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json
|
|
||||||
COPY package.json ./
|
|
||||||
|
|
||||||
# Install Nodemon
|
|
||||||
RUN npm install -g nodemon
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm i --no-package-lock
|
|
||||||
|
|
||||||
# Copy the rest of your application code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Expose the port your app runs on (adjust if necessary)
|
|
||||||
EXPOSE 4000 9229
|
|
||||||
|
|
||||||
# Start the application
|
|
||||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
|
||||||
@@ -2,7 +2,7 @@ NGROK TEsting:
|
|||||||
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
||||||
|
|
||||||
Finding deadfiles - run from client directory
|
Finding deadfiles - run from client directory
|
||||||
npx deadfile ./src/index.jsx --exclude build templates
|
npx deadfile ./src/index.js --exclude build templates
|
||||||
|
|
||||||
#Crushing all hasura migrations by creating a new initialization from the server.
|
#Crushing all hasura migrations by creating a new initialization from the server.
|
||||||
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||||
|
|||||||
@@ -10,8 +10,5 @@
|
|||||||
"courtesycars": "date",
|
"courtesycars": "date",
|
||||||
"media": "date",
|
"media": "date",
|
||||||
"visualboard": "date",
|
"visualboard": "date",
|
||||||
"scoreboard": "date",
|
"scoreboard": "date"
|
||||||
"checklist": "date",
|
|
||||||
"smartscheduling" :"date",
|
|
||||||
"roguard": "date"
|
|
||||||
}
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
|
||||||
|
|
||||||
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
1. **Windows 11**
|
|
||||||
2. **Docker Desktop For Windows (Latest Version)
|
|
||||||
|
|
||||||
# Docker Setup
|
|
||||||
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
|
||||||
`docker-compose up` to launch the backend.
|
|
||||||
|
|
||||||
Things to note:
|
|
||||||
- When installing NPM packages, you will need to rebuild the `node-app` container
|
|
||||||
- Making changes to the server files will restart the `node-app`
|
|
||||||
|
|
||||||
# Local Stack
|
|
||||||
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
|
||||||
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
|
||||||
|
|
||||||
# Docker Commands
|
|
||||||
|
|
||||||
## General `docker-compose` Commands:
|
|
||||||
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
|
||||||
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
|
||||||
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
|
||||||
4. Stop containers without removing them: `docker-compose stop`
|
|
||||||
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
|
||||||
6. Force rebuild of containers: `docker-compose build --no-cache`
|
|
||||||
7. View running Containers: `docker-compose ps`
|
|
||||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
|
||||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
|
||||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
|
||||||
|
|
||||||
## Volume Management Commands
|
|
||||||
1. List Docker volumes: `docker volume ls`
|
|
||||||
2. Remove Unused volumes `docker volume prune`
|
|
||||||
3. Remove specific volumes `docker volume rm <volume-name>`
|
|
||||||
4. Inspect a volume: `docker volume inspect <volume-name>`
|
|
||||||
|
|
||||||
## Container Image Management Commands:
|
|
||||||
1. List running containers: `docker ps`
|
|
||||||
2. List all containers: `docker os -a`
|
|
||||||
3. Remove Stopped containers: `docker container prune`
|
|
||||||
4. Remove a specific container: `docker container rm <container-name>`
|
|
||||||
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
|
||||||
6. Remove all unused images: `docker image prune -a`
|
|
||||||
|
|
||||||
## Network Management Commands:
|
|
||||||
1. List networks: `docker network ls`
|
|
||||||
2. Inspect a specific network: `docker network inspect <network-name>`
|
|
||||||
3. Remove a specific network: `docker network rm <network-name>`
|
|
||||||
4. Remove unused networks: `docker network prune`
|
|
||||||
|
|
||||||
## Debugging and maintenance:
|
|
||||||
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
|
||||||
2. View container resource usage: `docker stats`
|
|
||||||
3. Check Disk space used by Docker: `docker system df`
|
|
||||||
4. Remove all unused Data (Nuclear option): `docker system prune`
|
|
||||||
|
|
||||||
## Specific examples
|
|
||||||
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
|
||||||
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Production Board Notes:
|
|
||||||
|
|
||||||
## General Notes
|
|
||||||
|
|
||||||
- You can single click the lane footer to collapse/un-collapse the lane
|
|
||||||
- You can double click the lane header to collapse/un-collapse the lane
|
|
||||||
- If you need to scroll horizontally, you can hold shift and use the mouse scroll wheel, or press the mouse scroll wheel while scrolling
|
|
||||||
|
|
||||||
## Board Settings
|
|
||||||
|
|
||||||
#### Layout
|
|
||||||
|
|
||||||
- Board Orientation (Vertical or Horizontal)
|
|
||||||
- This determines the orientation of the card layout on the board.
|
|
||||||
- Horizontal is the default setting, and how the prior board was set up.
|
|
||||||
- Vertical is the new setting and allows lanes to be displayed vertically, with a grid of cards
|
|
||||||
- Card Size (Small, Medium, Large)
|
|
||||||
- This determines the size of the cards on the board.
|
|
||||||
- Small is the default setting, and how the prior board was set up.
|
|
||||||
- Medium and Large are new settings and allow for larger cards to be displayed on the board.
|
|
||||||
- Compact Cards (Tall or Wide)
|
|
||||||
- Formally called 'Compact'
|
|
||||||
- When on, data is displayed on the card vertically
|
|
||||||
- when turned off, some fields may share horizontal space, tightening the card layout
|
|
||||||
- Colored Cards (On or Off)
|
|
||||||
- When on, cards are colored based on the Status color
|
|
||||||
- Kiosk Mode (On or Off)
|
|
||||||
- This should be turned on if the shop is using it on a tablet (Ipad)
|
|
||||||
|
|
||||||
#### Information
|
|
||||||
|
|
||||||
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
|
||||||
|
|
||||||
|
|
||||||
### Statistics
|
|
||||||
|
|
||||||
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
|
|
||||||
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
|
|
||||||
|
|
||||||
### Filters
|
|
||||||
- Allows you to set, and persist filters for estimators and insurance companies
|
|
||||||
1
_reference/localEmailViewer/.gitignore
vendored
1
_reference/localEmailViewer/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
node_modules
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
This will connect to your dockers local stack session and render the email in HTML.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
node index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
http://localhost:3334
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// index.js
|
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import {simpleParser} from 'mailparser';
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const PORT = 3334;
|
|
||||||
|
|
||||||
app.get('/', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
const messagesHtml = await parseMessages(data.messages);
|
|
||||||
res.send(renderHtml(messagesHtml));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching messages:', error);
|
|
||||||
res.status(500).send('Error fetching messages');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function parseMessages(messages) {
|
|
||||||
const parsedMessages = await Promise.all(
|
|
||||||
messages.map(async (message, index) => {
|
|
||||||
try {
|
|
||||||
const parsed = await simpleParser(message.RawData);
|
|
||||||
return `
|
|
||||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
|
||||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">From:</span> ${message.Source}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Region:</span> ${message.Region}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="prose">
|
|
||||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing email:', error);
|
|
||||||
return `
|
|
||||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">From:</span> ${message.Source}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Region:</span> ${message.Region}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
|
||||||
</div>
|
|
||||||
<div class="text-red-500">
|
|
||||||
Error parsing email content
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return parsedMessages.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderHtml(messagesHtml) {
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Email Messages Viewer</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 50px auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.prose {
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
|
||||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
|
||||||
<div id="messages-container">
|
|
||||||
${messagesHtml}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
|
||||||
});
|
|
||||||
1214
_reference/localEmailViewer/package-lock.json
generated
1214
_reference/localEmailViewer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "localemailviewer",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": "",
|
|
||||||
"dependencies": {
|
|
||||||
"express": "^4.21.1",
|
|
||||||
"mailparser": "^3.7.1",
|
|
||||||
"node-fetch": "^3.3.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>IMEX IO Extractor</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.output-box {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
min-height: 40px;
|
|
||||||
}
|
|
||||||
.copy-button {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>IMEX IO Extractor</h1>
|
|
||||||
<textarea id="inputText" placeholder="Paste your text here..."></textarea>
|
|
||||||
<br>
|
|
||||||
<button onclick="extractIO()">Extract</button>
|
|
||||||
|
|
||||||
<div class="output-box" id="outputBox" contenteditable="true"></div>
|
|
||||||
<button class="copy-button" onclick="copyToClipboard()">Copy to Clipboard</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function extractIO() {
|
|
||||||
const inputText = document.getElementById('inputText').value;
|
|
||||||
const ioNumbers = [...new Set(inputText.match(/IO-\d{4}/g))] // Extract unique IO-#### matches
|
|
||||||
.map(io => ({ io, num: parseInt(io.split('-')[1]) })) // Extract number part for sorting
|
|
||||||
.sort((a, b) => a.num - b.num) // Sort by the number
|
|
||||||
.map(item => item.io); // Extract sorted IO-####
|
|
||||||
|
|
||||||
document.getElementById('outputBox').innerText = ioNumbers.join(', '); // Display horizontally
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard() {
|
|
||||||
const outputBox = document.getElementById('outputBox');
|
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(outputBox);
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection.removeAllRanges();
|
|
||||||
selection.addRange(range);
|
|
||||||
document.execCommand('copy');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,7 +4,7 @@ Clone Repository for:
|
|||||||
{
|
{
|
||||||
"name": "node-webhook-scripts",
|
"name": "node-webhook-scripts",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.jsx",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.16.4"
|
"express": "^4.16.4"
|
||||||
},
|
},
|
||||||
@@ -11,7 +11,7 @@ module.exports = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: "Bitbucket Webhook",
|
name: "Bitbucket Webhook",
|
||||||
script: "./webhook/index.jsx",
|
script: "./webhook/index.js",
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: "production"
|
NODE_ENV: "production"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
|
|
||||||
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
|
|
||||||
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
|
|
||||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
|
||||||
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
|
|
||||||
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
|
|
||||||
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
|
|
||||||
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
|
|
||||||
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
|
|
||||||
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
|
|
||||||
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
|
|
||||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
|
|
||||||
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
|
|
||||||
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
|
|
||||||
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
|
|
||||||
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
|
|
||||||
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
|
||||||
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
|
|
||||||
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
|
|
||||||
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
|
|
||||||
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
|
|
||||||
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
|
|
||||||
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
|
|
||||||
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
|
|
||||||
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
|
|
||||||
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
|
|
||||||
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
|
|
||||||
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
|
|
||||||
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
|
|
||||||
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
|
|
||||||
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
|
|
||||||
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
|
|
||||||
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
|
|
||||||
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
|
|
||||||
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
|
|
||||||
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
|
|
||||||
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
|
|
||||||
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
|
|
||||||
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
|
|
||||||
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
|
|
||||||
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
|
|
||||||
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBYJnAujo17diR0fM2Ze1d1Ft6XHm5
|
|
||||||
U31pXdFEN+rGC4SoYTdZE8q3relxMS5GwwBOvgvVUuayfid2XS8ls/CMDiMBJAYqEK4CRY
|
|
||||||
PbbPB7lLnMWsF7muFhvs+SIpPQC+vtDwM2TKlxF0Y8p+iVRpvCADoggsSze7skmJWKmMTt
|
|
||||||
8jEdEOcAAAEQIyXsOSMl7DkAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
|
|
||||||
AAAIUEAWCZwLo6Ne3YkdHzNmXtXdRbelx5uVN9aV3RRDfqxguEqGE3WRPKt63pcTEuRsMA
|
|
||||||
Tr4L1VLmsn4ndl0vJbPwjA4jASQGKhCuAkWD22zwe5S5zFrBe5rhYb7PkiKT0Avr7Q8DNk
|
|
||||||
ypcRdGPKfolUabwgA6IILEs3u7JJiVipjE7fIxHRDnAAAAQUO5dO9G7i0bxGTP0zV3eIwv
|
|
||||||
5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZPwYC0wLMW4bJAf+kjqUnj4wGocoTeAAAAD2
|
|
||||||
lvLWZ0cC10ZXN0LWtleQECAwQ=
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEXRjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w== io-ftp-test-key
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
PuTTY-User-Key-File-3: ecdsa-sha2-nistp521
|
|
||||||
Encryption: none
|
|
||||||
Comment: io-ftp-test-key
|
|
||||||
Public-Lines: 4
|
|
||||||
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt
|
|
||||||
2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+
|
|
||||||
J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEX
|
|
||||||
Rjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w==
|
|
||||||
Private-Lines: 2
|
|
||||||
AAAAQUO5dO9G7i0bxGTP0zV3eIwv5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZP
|
|
||||||
wYC0wLMW4bJAf+kjqUnj4wGocoTe
|
|
||||||
Private-MAC: d67001d47e13c43dc8bdb9c68a25356a96c1c4a6714f3c5a1836fca646b78b54
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
|
|
||||||
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
|
|
||||||
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
|
|
||||||
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
|
|
||||||
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
|
|
||||||
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
|
|
||||||
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
|
|
||||||
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
|
|
||||||
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
|
|
||||||
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
|
|
||||||
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
|
|
||||||
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
|
|
||||||
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
|
|
||||||
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
|
|
||||||
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
|
|
||||||
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
|
|
||||||
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
|
|
||||||
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
|
|
||||||
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
|
|
||||||
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
|
|
||||||
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
|
|
||||||
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
|
|
||||||
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
|
|
||||||
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
|
|
||||||
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
|
|
||||||
Y4U4XNTMt2fxUACqiL4SHA==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
|||||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
|||||||
15
client/.env.development.promanager
Normal file
15
client/.env.development.promanager
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||||
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||||
|
VITE_APP_GA_CODE=231099835
|
||||||
|
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||||
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||||
|
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||||
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
|
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||||
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
|
VITE_APP_INSTANCE=PROMANAGER
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
@@ -9,7 +9,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
|||||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_COUNTRY=USA
|
VITE_APP_COUNTRY=USA
|
||||||
|
|||||||
15
client/.env.production.promanager
Normal file
15
client/.env.production.promanager
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
GENERATE_SOURCEMAP=true
|
||||||
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||||
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
||||||
|
VITE_APP_GA_CODE=231103507
|
||||||
|
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||||
|
VITE_APP_CLOUDINARY_API_KEY=473322739956866
|
||||||
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
|
||||||
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
|
VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
||||||
|
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||||
|
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
|
VITE_APP_INSTANCE=PROMANAGER
|
||||||
15
client/.env.test.promanager
Normal file
15
client/.env.test.promanager
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
|
||||||
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
|
||||||
|
VITE_APP_GA_CODE=231099835
|
||||||
|
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||||
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||||
|
VITE_APP_CLOUDINARY_API_KEY=473322739956866
|
||||||
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
||||||
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
|
VITE_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
|
||||||
|
VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||||
|
VITE_APP_IS_TEST=true
|
||||||
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
|
VITE_APP_INSTANCE=PROMANAGER
|
||||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Sentry Config File
|
# Sentry Config File
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
/dev-dist
|
|
||||||
|
|||||||
53
client/craco.config.js
Normal file
53
client/craco.config.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// craco.config.js
|
||||||
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
|
const CracoLessPlugin = require("craco-less");
|
||||||
|
const { convertLegacyToken } = require("@ant-design/compatible/lib");
|
||||||
|
const { theme } = require("antd/lib");
|
||||||
|
|
||||||
|
const { defaultAlgorithm, defaultSeed } = theme;
|
||||||
|
|
||||||
|
const mapToken = defaultAlgorithm(defaultSeed);
|
||||||
|
const v4Token = convertLegacyToken(mapToken);
|
||||||
|
|
||||||
|
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
plugin: CracoLessPlugin,
|
||||||
|
options: {
|
||||||
|
lessLoaderOptions: {
|
||||||
|
lessOptions: {
|
||||||
|
modifyVars: { ...v4Token },
|
||||||
|
javascriptEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
webpack: {
|
||||||
|
configure: (webpackConfig) => {
|
||||||
|
return {
|
||||||
|
...webpackConfig,
|
||||||
|
// Required for Dev Server
|
||||||
|
devServer: {
|
||||||
|
...webpackConfig.devServer,
|
||||||
|
allowedHosts: "all"
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
...webpackConfig.optimization,
|
||||||
|
// Workaround for CircleCI bug caused by the number of CPUs shown
|
||||||
|
// https://github.com/facebook/create-react-app/issues/8320
|
||||||
|
minimizer: webpackConfig.optimization.minimizer.map((item) => {
|
||||||
|
if (item instanceof TerserPlugin) {
|
||||||
|
item.options.parallel = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
devtool: "source-map"
|
||||||
|
};
|
||||||
@@ -12,6 +12,6 @@ module.exports = defineConfig({
|
|||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
return require("./cypress/plugins/index.js")(on, config);
|
return require("./cypress/plugins/index.js")(on, config);
|
||||||
},
|
},
|
||||||
baseUrl: "https://localhost:3000"
|
baseUrl: "http://localhost:3000"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// This example plugins/index.jsx can be used to load plugins
|
// This example plugins/index.js can be used to load plugins
|
||||||
//
|
//
|
||||||
// You can change the location of this file or turn off loading
|
// You can change the location of this file or turn off loading
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// This example support/index.jsx is processed and
|
// This example support/index.js is processed and
|
||||||
// loaded automatically before your test files.
|
// loaded automatically before your test files.
|
||||||
//
|
//
|
||||||
// This is a great place to put global configuration and
|
// This is a great place to put global configuration and
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import globals from "globals";
|
|
||||||
import pluginJs from "@eslint/js";
|
|
||||||
import pluginReact from "eslint-plugin-react";
|
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config[]} */
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
files: ["**/*.{js,mjs,cjs,jsx}"]
|
|
||||||
},
|
|
||||||
{ languageOptions: { globals: globals.browser } },
|
|
||||||
pluginJs.configs.recommended,
|
|
||||||
{
|
|
||||||
...pluginReact.configs.flat.recommended,
|
|
||||||
rules: {
|
|
||||||
...pluginReact.configs.flat.recommended.rules,
|
|
||||||
"react/prop-types": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pluginReact.configs.flat["jsx-runtime"]
|
|
||||||
];
|
|
||||||
@@ -1,108 +1,154 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
|
||||||
<meta http-equiv="Expires" content="0" />
|
|
||||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||||
<link rel="icon" href="/favicon.png" />
|
<link rel="icon" href="/favicon.png" />
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||||
<link rel="icon" href="/ro-favicon.png" />
|
<link rel="icon" href="/ro-favicon.png" />
|
||||||
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||||
|
<link rel="icon" href="/pm/pm-favicon.ico" />
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#1690ff" />
|
<meta name="theme-color" content="#1690ff" />
|
||||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
<!-- TODO:AIo Update the individual logos for each.-->
|
||||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF" />
|
<link rel="apple-touch-icon" href="public/logo192.png" />
|
||||||
|
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||||
<meta name="description" content="ImEX Online" />
|
<meta name="description" content="ImEX Online" />
|
||||||
<title>ImEX Online</title>
|
<title>ImEX Online</title>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.$crisp = [];
|
window.$crisp = [];
|
||||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||||
(function () {
|
(function () {
|
||||||
d = document;
|
d = document;
|
||||||
s = d.createElement("script");
|
s = d.createElement('script');
|
||||||
s.src = "https://client.crisp.chat/l.js";
|
s.src = 'https://client.crisp.chat/l.js';
|
||||||
s.async = 1;
|
s.async = 1;
|
||||||
d.getElementsByTagName("head")[0].appendChild(s);
|
d.getElementsByTagName('head')[0].appendChild(s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||||
<meta name="description" content="Rome Online" />
|
<meta name="description" content="Rome Online" />
|
||||||
<title>Rome Online</title>
|
<title>Rome Online</title>
|
||||||
<script type="text/javascript" id="zsiqchat">
|
|
||||||
var $zoho = $zoho || {};
|
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||||
$zoho.salesiq = $zoho.salesiq || {
|
|
||||||
widgetcode: "siq01bb8ac617280bdacddfeb528f07734dadc64ef3f05efef9f769c1ec171af666",
|
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
||||||
values: {},
|
|
||||||
ready: function () {}
|
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||||
};
|
|
||||||
var d = document;
|
<!--<call-us
|
||||||
s = d.createElement("script");
|
|
||||||
s.type = "text/javascript";
|
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||||
s.id = "zsiqscript";
|
|
||||||
s.defer = true;
|
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||||
s.src = "https://salesiq.zohopublic.com/widget";
|
|
||||||
t = d.getElementsByTagName("script")[0];
|
id="wp-live-chat-by-3CX"
|
||||||
t.parentNode.insertBefore(s, t);
|
|
||||||
</script>
|
minimized="true"
|
||||||
<script type="text/javascript">
|
|
||||||
window.$crisp = [];
|
animation-style="noanimation"
|
||||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
|
||||||
(function () {
|
party="LiveChat528346"
|
||||||
d = document;
|
|
||||||
s = d.createElement("script");
|
minimized-style="bubbleright"
|
||||||
s.src = "https://client.crisp.chat/l.js";
|
|
||||||
s.async = 1;
|
allow-call="true"
|
||||||
d.getElementsByTagName("head")[0].appendChild(s);
|
|
||||||
})();
|
allow-video="false"
|
||||||
</script>
|
|
||||||
|
allow-soundnotifications="true"
|
||||||
|
|
||||||
|
enable-mute="true"
|
||||||
|
|
||||||
|
enable-onmobile="true"
|
||||||
|
|
||||||
|
offline-enabled="true"
|
||||||
|
|
||||||
|
enable="true"
|
||||||
|
|
||||||
|
ignore-queueownership="false"
|
||||||
|
|
||||||
|
authentication="both"
|
||||||
|
|
||||||
|
show-operator-actual-name="true"
|
||||||
|
|
||||||
|
aknowledge-received="true"
|
||||||
|
|
||||||
|
gdpr-enabled="false"
|
||||||
|
|
||||||
|
message-userinfo-format="name"
|
||||||
|
|
||||||
|
message-dateformat="both"
|
||||||
|
|
||||||
|
lang="browser"
|
||||||
|
|
||||||
|
button-icon-type="default"
|
||||||
|
|
||||||
|
greeting-visibility="none"
|
||||||
|
|
||||||
|
greeting-offline-visibility="none"
|
||||||
|
|
||||||
|
chat-delay="2000"
|
||||||
|
|
||||||
|
enable-direct-call="true"
|
||||||
|
|
||||||
|
enable-ga="false"
|
||||||
|
|
||||||
|
></call-us>-->
|
||||||
|
|
||||||
|
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||||
|
<title>ProManager</title>
|
||||||
|
<meta name="description" content="ProManager" />
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
||||||
<script>
|
<script>
|
||||||
!(function () {
|
!(function () {
|
||||||
"use strict";
|
'use strict';
|
||||||
var e = [
|
var e = [
|
||||||
"debug",
|
'debug',
|
||||||
"destroy",
|
'destroy',
|
||||||
"do",
|
'do',
|
||||||
"help",
|
'help',
|
||||||
"identify",
|
'identify',
|
||||||
"is",
|
'is',
|
||||||
"off",
|
'off',
|
||||||
"on",
|
'on',
|
||||||
"ready",
|
'ready',
|
||||||
"render",
|
'render',
|
||||||
"reset",
|
'reset',
|
||||||
"safe",
|
'safe',
|
||||||
"set"
|
'set',
|
||||||
];
|
];
|
||||||
if (window.noticeable) console.warn("Noticeable SDK code snippet loaded more than once");
|
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||||
else {
|
else {
|
||||||
var n = (window.noticeable = window.noticeable || []);
|
var n = (window.noticeable = window.noticeable || []);
|
||||||
|
|
||||||
function t(e) {
|
function t(e) {
|
||||||
return function () {
|
return function () {
|
||||||
var t = Array.prototype.slice.call(arguments);
|
var t = Array.prototype.slice.call(arguments);
|
||||||
return t.unshift(e), n.push(t), n;
|
return t.unshift(e), n.push(t), n;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
!(function () {
|
!(function () {
|
||||||
for (var o = 0; o < e.length; o++) {
|
for (var o = 0; o < e.length; o++) {
|
||||||
var r = e[o];
|
var r = e[o];
|
||||||
@@ -110,8 +156,8 @@
|
|||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
(function () {
|
(function () {
|
||||||
var e = document.createElement("script");
|
var e = document.createElement('script');
|
||||||
(e.async = !0), (e.src = "https://sdk.noticeable.io/l.js");
|
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||||
var n = document.head;
|
var n = document.head;
|
||||||
n.insertBefore(e, n.firstChild);
|
n.insertBefore(e, n.firstChild);
|
||||||
})();
|
})();
|
||||||
|
|||||||
23761
client/package-lock.json
generated
23761
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,105 +2,112 @@
|
|||||||
"name": "bodyshop",
|
"name": "bodyshop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.2"
|
"node": "18.18.2"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^7.22.0",
|
"@ant-design/compatible": "^5.1.2",
|
||||||
"@apollo/client": "^3.12.6",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@apollo/client": "^3.8.10",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.5.1",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.5.0",
|
"@reduxjs/toolkit": "^2.2.1",
|
||||||
"@sentry/cli": "^2.40.0",
|
"@sentry/cli": "^2.28.6",
|
||||||
"@sentry/react": "^7.114.0",
|
"@sentry/react": "^7.104.0",
|
||||||
"@splitsoftware/splitio-react": "^1.13.0",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.15.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"autosize": "^6.0.1",
|
"axios": "^1.6.7",
|
||||||
"axios": "^1.7.9",
|
"dayjs": "^1.11.10",
|
||||||
"classnames": "^2.5.1",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"css-box-model": "^1.2.1",
|
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"dayjs-business-days2": "^1.2.3",
|
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.5",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.13.2",
|
"firebase": "^10.8.1",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.15.1",
|
"i18next": "^23.10.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"immutability-helper": "^3.1.1",
|
"libphonenumber-js": "^1.10.57",
|
||||||
"libphonenumber-js": "^1.11.18",
|
"logrocket": "^8.0.1",
|
||||||
"logrocket": "^8.1.2",
|
"markerjs2": "^2.32.0",
|
||||||
"markerjs2": "^2.32.3",
|
"normalize-url": "^8.0.0",
|
||||||
"memoize-one": "^6.0.0",
|
|
||||||
"normalize-url": "^8.0.1",
|
|
||||||
"object-hash": "^3.0.0",
|
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.1.1",
|
"query-string": "^9.0.0",
|
||||||
"raf-schd": "^4.0.3",
|
"react": "^18.2.0",
|
||||||
"react": "^18.3.1",
|
"react-big-calendar": "^1.11.0",
|
||||||
"react-big-calendar": "^1.17.1",
|
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.2.2",
|
"react-cookie": "^7.1.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.2.0",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.0",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.0.5",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.0.1",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^9.0.3",
|
"react-joyride": "^2.7.4",
|
||||||
"react-number-format": "^5.4.3",
|
"react-markdown": "^9.0.1",
|
||||||
"react-popopo": "^2.1.9",
|
"react-number-format": "^5.3.3",
|
||||||
"react-product-fruits": "^2.2.61",
|
"react-product-fruits": "^2.2.6",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.22.2",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.10.4",
|
"react-virtualized": "^9.22.5",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.12.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.3",
|
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.0",
|
||||||
"sass": "^1.83.4",
|
"sass": "^1.71.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.7.4",
|
||||||
"styled-components": "^6.1.14",
|
"styled-components": "^6.1.8",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"userpilot": "^1.3.6",
|
"userpilot": "^1.3.1",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
"web-vitals": "^3.5.2"
|
"web-vitals": "^3.5.2",
|
||||||
|
"workbox-core": "^7.0.0",
|
||||||
|
"workbox-expiration": "^7.0.0",
|
||||||
|
"workbox-navigation-preload": "^7.0.0",
|
||||||
|
"workbox-precaching": "^7.0.0",
|
||||||
|
"workbox-routing": "^7.0.0",
|
||||||
|
"workbox-strategies": "^7.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "echo 'when updating react-big-calendar, remember to check to localizer in the calendar wrapper'",
|
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "dotenvx run --env-file=.env.development.imex -- vite build",
|
"build": "vite build",
|
||||||
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
||||||
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
||||||
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
|
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
|
||||||
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
|
|
||||||
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
||||||
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
||||||
|
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
||||||
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
||||||
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
||||||
|
"build:production:promanager": "env-cmd -f .env.production.promanager npm run build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
"eulaize": "node src/utils/eulaize.js",
|
"eulaize": "node src/utils/eulaize.js",
|
||||||
"sentry:sourcemaps:imex": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
"sentry:sourcemaps:imex": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||||
},
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest",
|
||||||
|
"plugin:cypress/recommended"
|
||||||
|
]
|
||||||
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
@@ -120,36 +127,33 @@
|
|||||||
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^5.5.2",
|
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.26.3",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"@dotenvx/dotenvx": "^1.33.0",
|
"@dotenvx/dotenvx": "^0.15.4",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.11.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.11.3",
|
||||||
"@eslint/js": "^9.18.0",
|
"@sentry/webpack-plugin": "^2.14.2",
|
||||||
"@sentry/webpack-plugin": "^2.22.4",
|
"@swc/core": "^1.3.107",
|
||||||
"@testing-library/cypress": "^10.0.2",
|
"@swc/plugin-styled-components": "^1.5.108",
|
||||||
"browserslist": "^4.24.4",
|
"@testing-library/cypress": "^10.0.1",
|
||||||
|
"browserslist": "^4.22.3",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.4.1",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.17.0",
|
"cypress": "^13.6.6",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"memfs": "^4.6.0",
|
||||||
"globals": "^15.14.0",
|
|
||||||
"memfs": "^4.17.0",
|
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^6.0.7",
|
"vite": "^5.0.11",
|
||||||
"vite-plugin-babel": "^1.3.0",
|
"vite-plugin-babel": "^1.2.0",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.23.0",
|
"vite-plugin-legacy": "^2.1.0",
|
||||||
"vite-plugin-pwa": "^0.21.1",
|
"vite-plugin-node-polyfills": "^0.19.0",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-pwa": "^0.19.0",
|
||||||
"workbox-window": "^7.3.0"
|
"vite-plugin-style-import": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16422,7 +16422,7 @@ For when you don't want to write the same thing over and over to cache a method
|
|||||||
$ npm install --save-dev stubs
|
$ npm install --save-dev stubs
|
||||||
```
|
```
|
||||||
```js
|
```js
|
||||||
var mylib = require('./lib/index.jsx')
|
var mylib = require('./lib/index.js')
|
||||||
var stubs = require('stubs')
|
var stubs = require('stubs')
|
||||||
|
|
||||||
// make it a noop
|
// make it a noop
|
||||||
|
|||||||
@@ -16567,7 +16567,7 @@ even more slower.
|
|||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ node benchmarks/index.jsx
|
$ node benchmarks/index.js
|
||||||
Benchmarking: sign
|
Benchmarking: sign
|
||||||
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
|
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
|
||||||
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)
|
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Scripts for firebase and firebase messaging
|
// Scripts for firebase and firebase messaging
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
|
||||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
|
||||||
|
|
||||||
// Initialize the Firebase app in the service worker by passing the generated config
|
// Initialize the Firebase app in the service worker by passing the generated config
|
||||||
let firebaseConfig;
|
let firebaseConfig;
|
||||||
@@ -14,7 +14,7 @@ switch (this.location.hostname) {
|
|||||||
storageBucket: "imex-dev.appspot.com",
|
storageBucket: "imex-dev.appspot.com",
|
||||||
messagingSenderId: "759548147434",
|
messagingSenderId: "759548147434",
|
||||||
appId: "1:759548147434:web:e8239868a48ceb36700993",
|
appId: "1:759548147434:web:e8239868a48ceb36700993",
|
||||||
measurementId: "G-K5XRBVVB4S"
|
measurementId: "G-K5XRBVVB4S",
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "test.imex.online":
|
case "test.imex.online":
|
||||||
@@ -24,7 +24,7 @@ switch (this.location.hostname) {
|
|||||||
projectId: "imex-test",
|
projectId: "imex-test",
|
||||||
storageBucket: "imex-test.appspot.com",
|
storageBucket: "imex-test.appspot.com",
|
||||||
messagingSenderId: "991923618608",
|
messagingSenderId: "991923618608",
|
||||||
appId: "1:991923618608:web:633437569cdad78299bef5"
|
appId: "1:991923618608:web:633437569cdad78299bef5",
|
||||||
// measurementId: "${config.measurementId}",
|
// measurementId: "${config.measurementId}",
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -38,7 +38,7 @@ switch (this.location.hostname) {
|
|||||||
storageBucket: "imex-prod.appspot.com",
|
storageBucket: "imex-prod.appspot.com",
|
||||||
messagingSenderId: "253497221485",
|
messagingSenderId: "253497221485",
|
||||||
appId: "1:253497221485:web:3c81c483b94db84b227a64",
|
appId: "1:253497221485:web:3c81c483b94db84b227a64",
|
||||||
measurementId: "G-NTWBKG2L0M"
|
measurementId: "G-NTWBKG2L0M",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +49,8 @@ const messaging = firebase.messaging();
|
|||||||
|
|
||||||
messaging.onBackgroundMessage(function (payload) {
|
messaging.onBackgroundMessage(function (payload) {
|
||||||
// Customize notification here
|
// Customize notification here
|
||||||
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
const channel = new BroadcastChannel("imex-sw-messages");
|
||||||
self.registration.showNotification(notificationTitle, notificationOptions);
|
channel.postMessage(payload);
|
||||||
|
|
||||||
|
//self.registration.showNotification(notificationTitle, notificationOptions);
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
client/public/pm/pm-apple-touch-icon.png
Normal file
BIN
client/public/pm/pm-apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
client/public/pm/pm-favicon.ico
Normal file
BIN
client/public/pm/pm-favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
client/public/pm/pm-icon-192-maskable.png
Normal file
BIN
client/public/pm/pm-icon-192-maskable.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
client/public/pm/pm-icon-192.png
Normal file
BIN
client/public/pm/pm-icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
client/public/pm/pm-icon-512-maskable.png
Normal file
BIN
client/public/pm/pm-icon-512-maskable.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
client/public/pm/pm-icon-512.png
Normal file
BIN
client/public/pm/pm-icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -2,6 +2,8 @@ import { ApolloProvider } from "@apollo/client";
|
|||||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
||||||
import { ConfigProvider } from "antd";
|
import { ConfigProvider } from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
|
import dayjs from "../utils/day";
|
||||||
|
import "dayjs/locale/en";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
@@ -17,6 +19,8 @@ if (import.meta.env.DEV) {
|
|||||||
Userpilot.initialize("NX-69145f08");
|
Userpilot.initialize("NX-69145f08");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dayjs.locale("en");
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
core: {
|
core: {
|
||||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { connect } from "react-redux";
|
|||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||||
|
|
||||||
|
//Component Imports
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||||
import LandingPage from "../pages/landing/landing.page";
|
import LandingPage from "../pages/landing/landing.page";
|
||||||
@@ -18,25 +20,23 @@ import { checkUserSession } from "../redux/user/user.actions";
|
|||||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
|
import handleBeta from "../utils/betaHandler";
|
||||||
import Eula from "../components/eula/eula.component";
|
import Eula from "../components/eula/eula.component";
|
||||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import { ProductFruits } from "react-product-fruits";
|
||||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
|
||||||
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||||
|
|
||||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||||
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentEula: selectCurrentEula
|
currentEula: selectCurrentEula
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
||||||
@@ -60,11 +60,11 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
|
|
||||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const offlineListener = () => {
|
const offlineListener = (e) => {
|
||||||
setOnline(false);
|
setOnline(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onlineListener = () => {
|
const onlineListener = (e) => {
|
||||||
setOnline(true);
|
setOnline(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
LogRocket.init(
|
LogRocket.init(
|
||||||
InstanceRenderMgr({
|
InstanceRenderMgr({
|
||||||
imex: "gvfvfw/bodyshopapp",
|
imex: "gvfvfw/bodyshopapp",
|
||||||
rome: "rome-online/rome-online"
|
rome: "rome-online/rome-online",
|
||||||
|
promanager: "" //TODO:AIO Add in log rocket for promanager instances.
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -108,20 +109,26 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!online) {
|
handleBeta();
|
||||||
|
|
||||||
|
if (!online)
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status="warning"
|
status="warning"
|
||||||
title={t("general.labels.nointernet")}
|
title={t("general.labels.nointernet")}
|
||||||
subTitle={t("general.labels.nointernet_sub")}
|
subTitle={t("general.labels.nointernet_sub")}
|
||||||
extra={
|
extra={
|
||||||
<Button type="primary" onClick={() => window.location.reload()}>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("general.actions.refresh")}
|
{t("general.actions.refresh")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||||
return <Eula />;
|
return <Eula />;
|
||||||
@@ -134,97 +141,99 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
message={InstanceRenderMgr({
|
message={InstanceRenderMgr({
|
||||||
imex: t("titles.imexonline"),
|
imex: t("titles.imexonline"),
|
||||||
rome: t("titles.romeonline")
|
rome: t("titles.romeonline"),
|
||||||
|
promanager: t("titles.promanager")
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ProductFruitsWrapper
|
<ProductFruits
|
||||||
currentUser={currentUser}
|
|
||||||
workspaceCode={InstanceRenderMgr({
|
workspaceCode={InstanceRenderMgr({
|
||||||
imex: null,
|
imex: null,
|
||||||
rome: "9BkbEseqNqxw8jUH"
|
rome: "9BkbEseqNqxw8jUH",
|
||||||
|
promanager: "aoJoEifvezYI0Z0P"
|
||||||
})}
|
})}
|
||||||
|
debug
|
||||||
|
language="en"
|
||||||
|
user={{
|
||||||
|
email: currentUser.email,
|
||||||
|
username: currentUser.email
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<NotificationProvider>
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<LandingPage />
|
<LandingPage />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/signin"
|
path="/signin"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<SignInPage />
|
<SignInPage />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/resetpassword"
|
path="/resetpassword"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ResetPassword />
|
<ResetPassword />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/csi/:surveyId"
|
path="/csi/:surveyId"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<CsiPage />
|
<CsiPage />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/disclaimer"
|
path="/disclaimer"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<DisclaimerPage />
|
<DisclaimerPage />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/mp/:paymentIs"
|
path="/mp/:paymentIs"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<MobilePaymentContainer />
|
<MobilePaymentContainer />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/manage/*"
|
path="/manage/*"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<SocketProvider bodyshop={bodyshop}>
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
</ErrorBoundary>
|
||||||
</SocketProvider>
|
}
|
||||||
</ErrorBoundary>
|
>
|
||||||
}
|
<Route path="*" element={<ManagePage />} />
|
||||||
>
|
</Route>
|
||||||
<Route path="*" element={<ManagePage />} />
|
<Route
|
||||||
</Route>
|
path="/tech/*"
|
||||||
<Route
|
element={
|
||||||
path="/tech/*"
|
<ErrorBoundary>
|
||||||
element={
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
<ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<SocketProvider bodyshop={bodyshop}>
|
}
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
>
|
||||||
</SocketProvider>
|
<Route path="*" element={<TechPageContainer />} />
|
||||||
</ErrorBoundary>
|
</Route>
|
||||||
}
|
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
||||||
>
|
<Route path="*" element={<DocumentEditorContainer />} />
|
||||||
<Route path="*" element={<TechPageContainer />} />
|
</Route>
|
||||||
</Route>
|
</Routes>
|
||||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
|
||||||
<Route path="*" element={<DocumentEditorContainer />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</NotificationProvider>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,6 @@
|
|||||||
border-bottom: 1px solid #74695c !important;
|
border-bottom: 1px solid #74695c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This was added because the newest release of ant was making the text color and the background color the same on a selected header
|
|
||||||
// Tried all available tokens (https://ant.design/components/menu?locale=en-US) and even reverted all our custom styles, to no avail
|
|
||||||
// This should be kept an eye on, especially if implementing DARK MODE
|
|
||||||
.ant-menu-submenu-title {
|
|
||||||
color: rgba(255, 255, 255, 0.65) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -168,15 +161,3 @@
|
|||||||
.rowWithColor > td {
|
.rowWithColor > td {
|
||||||
background-color: var(--bgColor) !important;
|
background-color: var(--bgColor) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.muted-button {
|
|
||||||
color: lightgray;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px; /* Adjust as needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted-button:hover {
|
|
||||||
color: darkgrey;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { ProductFruits } from "react-product-fruits";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
|
||||||
return (
|
|
||||||
workspaceCode &&
|
|
||||||
currentUser?.authorized === true &&
|
|
||||||
currentUser?.email && (
|
|
||||||
<ProductFruits
|
|
||||||
lifeCycle="unmount"
|
|
||||||
workspaceCode={workspaceCode}
|
|
||||||
debug
|
|
||||||
language="en"
|
|
||||||
user={{
|
|
||||||
email: currentUser.email,
|
|
||||||
username: currentUser.email
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ProductFruitsWrapper;
|
|
||||||
|
|
||||||
ProductFruitsWrapper.propTypes = {
|
|
||||||
currentUser: PropTypes.shape({
|
|
||||||
authorized: PropTypes.bool,
|
|
||||||
email: PropTypes.string
|
|
||||||
}),
|
|
||||||
workspaceCode: PropTypes.string
|
|
||||||
};
|
|
||||||
@@ -26,11 +26,13 @@ const defaultTheme = {
|
|||||||
token: {
|
token: {
|
||||||
colorPrimary: InstanceRenderMgr({
|
colorPrimary: InstanceRenderMgr({
|
||||||
imex: "#1890ff",
|
imex: "#1890ff",
|
||||||
rome: "#326ade"
|
rome: "#326ade",
|
||||||
|
promanager: "#1d69a6"
|
||||||
}),
|
}),
|
||||||
colorInfo: InstanceRenderMgr({
|
colorInfo: InstanceRenderMgr({
|
||||||
imex: "#1890ff",
|
imex: "#1890ff",
|
||||||
rome: "#326ade"
|
rome: "#326ade",
|
||||||
|
promanager: "#1d69a6"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { exportPageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||||
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
pagination={{ position: "top", pageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { exportPageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -85,17 +85,6 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
|
||||||
render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter>
|
render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter>
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("payments.fields.type"),
|
|
||||||
dataIndex: "type",
|
|
||||||
key: "type",
|
|
||||||
sorter: (a, b) => a.type.localeCompare(b.type),
|
|
||||||
sortOrder: state.sortedInfo.columnKey === "type" && state.sortedInfo.order,
|
|
||||||
filters: bodyshop.md_payment_types.map((s) => {
|
|
||||||
return { text: s, value: [s] };
|
|
||||||
}),
|
|
||||||
onFilter: (value, record) => value.includes(record.type)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("payments.fields.memo"),
|
title: t("payments.fields.memo"),
|
||||||
dataIndex: "memo",
|
dataIndex: "memo",
|
||||||
@@ -188,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
pagination={{ position: "top", pageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { Button, Card, Input, Space, Table } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { exportPageLimit } from "../../utils/config";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
|
||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
|
||||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
|
||||||
@@ -171,22 +170,13 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||||
<>
|
<JobsExportAllButton
|
||||||
<JobMarkSelectedExported
|
jobIds={selectedJobs}
|
||||||
jobIds={selectedJobs}
|
disabled={transInProgress || selectedJobs.length === 0}
|
||||||
disabled={transInProgress || selectedJobs.length === 0}
|
loadingCallback={setTransInProgress}
|
||||||
loadingCallback={setTransInProgress}
|
completedCallback={setSelectedJobs}
|
||||||
completedCallback={setSelectedJobs}
|
refetch={refetch}
|
||||||
refetch={refetch}
|
/>
|
||||||
/>
|
|
||||||
<JobsExportAllButton
|
|
||||||
jobIds={selectedJobs}
|
|
||||||
disabled={transInProgress || selectedJobs.length === 0}
|
|
||||||
loadingCallback={setTransInProgress}
|
|
||||||
completedCallback={setSelectedJobs}
|
|
||||||
refetch={refetch}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
||||||
<Input.Search
|
<Input.Search
|
||||||
@@ -201,7 +191,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
pagination={{ position: "top" }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { notification } from "antd";
|
||||||
|
|
||||||
export default function AllocationsAssignmentContainer({ jobLineId, hours, refetch }) {
|
export default function AllocationsAssignmentContainer({ jobLineId, hours, refetch }) {
|
||||||
const visibilityState = useState(false);
|
const visibilityState = useState(false);
|
||||||
@@ -14,7 +14,6 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours, refet
|
|||||||
employeeid: null
|
employeeid: null
|
||||||
});
|
});
|
||||||
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const handleAssignment = () => {
|
const handleAssignment = () => {
|
||||||
insertAllocation({ variables: { alloc: { ...assignment } } })
|
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { notification } from "antd";
|
||||||
|
|
||||||
export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }) {
|
export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }) {
|
||||||
const visibilityState = useState(false);
|
const visibilityState = useState(false);
|
||||||
@@ -12,7 +12,6 @@ export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }
|
|||||||
employeeid: null
|
employeeid: null
|
||||||
});
|
});
|
||||||
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
const [insertAllocation] = useMutation(INSERT_ALLOCATION);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const handleAssignment = () => {
|
const handleAssignment = () => {
|
||||||
const allocs = jobLines.reduce((acc, value) => {
|
const allocs = jobLines.reduce((acc, value) => {
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import React from "react";
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||||
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||||
|
import { notification } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
export default function AllocationsLabelContainer({ allocation, refetch }) {
|
export default function AllocationsLabelContainer({ allocation, refetch }) {
|
||||||
const [deleteAllocation] = useMutation(DELETE_ALLOCATION);
|
const [deleteAllocation] = useMutation(DELETE_ALLOCATION);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const handleClick = (e) => {
|
const handleClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Popconfirm } from "antd";
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||||
@@ -9,7 +9,6 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -22,7 +21,6 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [deleteBill] = useMutation(DELETE_BILL);
|
const [deleteBill] = useMutation(DELETE_BILL);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PageHeader } from "@ant-design/pro-layout";
|
|
||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -13,7 +13,6 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
|||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||||
@@ -23,6 +22,7 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
|||||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
});
|
});
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
billlines.forEach((billline) => {
|
||||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||||
delete il.__typename;
|
delete il.__typename;
|
||||||
|
|
||||||
if (il.id) {
|
if (il.id) {
|
||||||
@@ -153,7 +153,6 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||||
|
|
||||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||||
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -189,7 +188,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
||||||
<BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse} />
|
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||||
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
||||||
{bodyshop.uselocalmediaserver ? (
|
{bodyshop.uselocalmediaserver ? (
|
||||||
<JobsDocumentsLocalGallery
|
<JobsDocumentsLocalGallery
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -24,7 +24,6 @@ import BillFormContainer from "../bill-form/bill-form.container";
|
|||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -50,7 +49,6 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
const [generateLabel, setGenerateLabel] = useLocalStorage("enter_bill_generate_label", false);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Enhanced_Payroll }
|
treatments: { Enhanced_Payroll }
|
||||||
@@ -293,8 +291,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
jobid: values.jobid,
|
jobid: values.jobid,
|
||||||
invoice_number: remainingValues.invoice_number,
|
invoice_number: remainingValues.invoice_number,
|
||||||
vendorid: remainingValues.vendorid
|
vendorid: remainingValues.vendorid
|
||||||
},
|
}
|
||||||
notification
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -308,8 +305,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
billId: billId,
|
billId: billId,
|
||||||
tagsArray: null,
|
tagsArray: null,
|
||||||
callback: null
|
callback: null
|
||||||
},
|
}
|
||||||
notification
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -329,9 +325,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
"p",
|
"p"
|
||||||
null,
|
|
||||||
notification
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import dayjs from "../../utils/day";
|
|||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
@@ -21,7 +22,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -41,8 +41,7 @@ export function BillFormComponent({
|
|||||||
job,
|
job,
|
||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
preferredMake,
|
preferredMake
|
||||||
disableInHouse
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -178,7 +177,7 @@ export function BillFormComponent({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<VendorSearchSelect
|
<VendorSearchSelect
|
||||||
disabled={disabled || disableInHouse}
|
disabled={disabled}
|
||||||
options={vendorAutoCompleteOptions}
|
options={vendorAutoCompleteOptions}
|
||||||
preferredMake={preferredMake}
|
preferredMake={preferredMake}
|
||||||
onSelect={handleVendorSelect}
|
onSelect={handleVendorSelect}
|
||||||
@@ -244,7 +243,7 @@ export function BillFormComponent({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input disabled={disabled || disableInvNumber || disableInHouse} />
|
<Input disabled={disabled || disableInvNumber} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bills.fields.date")}
|
label={t("bills.fields.date")}
|
||||||
@@ -276,7 +275,7 @@ export function BillFormComponent({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<DateTimePicker isDateOnly disabled={disabled} />
|
<FormDatePicker disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bills.fields.is_credit_memo")}
|
label={t("bills.fields.is_credit_memo")}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) {
|
||||||
const {
|
const {
|
||||||
treatments: { Simple_Inventory }
|
treatments: { Simple_Inventory }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
@@ -47,7 +47,6 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
|||||||
job={lineData ? lineData.jobs_by_pk : null}
|
job={lineData ? lineData.jobs_by_pk : null}
|
||||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||||
disableInvNumber={disableInvNumber}
|
disableInvNumber={disableInvNumber}
|
||||||
disableInHouse={disableInHouse}
|
|
||||||
loadOutstandingReturns={loadOutstandingReturns}
|
loadOutstandingReturns={loadOutstandingReturns}
|
||||||
loadInventory={loadInventory}
|
loadInventory={loadInventory}
|
||||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -72,14 +72,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
<BillLineSearchSelect
|
<BillLineSearchSelect
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={lineData}
|
options={lineData}
|
||||||
style={{
|
style={{ width: "100%", minWidth: "10rem" }}
|
||||||
width: "20rem",
|
|
||||||
maxWidth: "20rem",
|
|
||||||
minWidth: "10rem",
|
|
||||||
whiteSpace: "normal",
|
|
||||||
height: "auto",
|
|
||||||
minHeight: "32px" // default height of Ant Design inputs
|
|
||||||
}}
|
|
||||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||||
onSelect={(value, opt) => {
|
onSelect={(value, opt) => {
|
||||||
setFieldsValue({
|
setFieldsValue({
|
||||||
@@ -112,7 +105,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
title: t("billlines.fields.line_desc"),
|
title: t("billlines.fields.line_desc"),
|
||||||
dataIndex: "line_desc",
|
dataIndex: "line_desc",
|
||||||
editable: true,
|
editable: true,
|
||||||
width: "20rem",
|
|
||||||
formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
return {
|
return {
|
||||||
key: `${field.index}line_desc`,
|
key: `${field.index}line_desc`,
|
||||||
@@ -126,7 +119,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
formInput: (record, index) => <Input disabled={disabled} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.quantity"),
|
title: t("billlines.fields.quantity"),
|
||||||
@@ -228,6 +221,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
|
//Do not need to set for promanager as it will default to Rome.
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -461,6 +455,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
...InstanceRenderManager({
|
...InstanceRenderManager({
|
||||||
rome: [],
|
rome: [],
|
||||||
|
promanager: [],
|
||||||
imex: [
|
imex: [
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.federal_tax_applicable"),
|
title: t("billlines.fields.federal_tax_applicable"),
|
||||||
@@ -473,7 +468,8 @@ export function BillEnterModalLinesComponent({
|
|||||||
valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
initialValue: InstanceRenderManager({
|
initialValue: InstanceRenderManager({
|
||||||
imex: true,
|
imex: true,
|
||||||
rome: false
|
rome: false,
|
||||||
|
promanager: false
|
||||||
}),
|
}),
|
||||||
name: [field.name, "applicable_taxes", "federal"]
|
name: [field.name, "applicable_taxes", "federal"]
|
||||||
};
|
};
|
||||||
@@ -500,6 +496,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
...InstanceRenderManager({
|
...InstanceRenderManager({
|
||||||
rome: [],
|
rome: [],
|
||||||
|
promanager: [],
|
||||||
imex: [
|
imex: [
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.local_tax_applicable"),
|
title: t("billlines.fields.local_tax_applicable"),
|
||||||
@@ -625,15 +622,11 @@ const EditableCell = ({
|
|||||||
wrapper,
|
wrapper,
|
||||||
...restProps
|
...restProps
|
||||||
}) => {
|
}) => {
|
||||||
const propsFinal = formItemProps && formItemProps(record);
|
|
||||||
if (propsFinal && "key" in propsFinal) {
|
|
||||||
delete propsFinal.key;
|
|
||||||
}
|
|
||||||
if (additional)
|
if (additional)
|
||||||
return (
|
return (
|
||||||
<td {...restProps}>
|
<td {...restProps}>
|
||||||
<div size="small">
|
<div size="small">
|
||||||
<Form.Item name={dataIndex} labelCol={{ span: 0 }} {...propsFinal}>
|
<Form.Item name={dataIndex} labelCol={{ span: 0 }} {...(formItemProps && formItemProps(record))}>
|
||||||
{(formInput && formInput(record, record.name)) || children}
|
{(formInput && formInput(record, record.name)) || children}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{additional && additional(record, record.name)}
|
{additional && additional(record, record.name)}
|
||||||
@@ -644,7 +637,7 @@ const EditableCell = ({
|
|||||||
return (
|
return (
|
||||||
<wrapper>
|
<wrapper>
|
||||||
<td {...restProps}>
|
<td {...restProps}>
|
||||||
<Form.Item labelCol={{ span: 0 }} name={dataIndex} {...propsFinal}>
|
<Form.Item labelCol={{ span: 0 }} name={dataIndex} {...(formItemProps && formItemProps(record))}>
|
||||||
{(formInput && formInput(record, record.name)) || children}
|
{(formInput && formInput(record, record.name)) || children}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
@@ -652,7 +645,7 @@ const EditableCell = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<td {...restProps}>
|
<td {...restProps}>
|
||||||
<Form.Item labelCol={{ span: 0 }} name={dataIndex} {...propsFinal}>
|
<Form.Item labelCol={{ span: 0 }} name={dataIndex} {...(formItemProps && formItemProps(record))}>
|
||||||
{(formInput && formInput(record, record.name)) || children}
|
{(formInput && formInput(record, record.name)) || children}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import React, { forwardRef } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
|
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
|
//To be used as a form element only.
|
||||||
|
const { Option } = Select;
|
||||||
const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => {
|
const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -11,7 +13,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
showSearch
|
showSearch
|
||||||
popupMatchSelectWidth={true}
|
popupMatchSelectWidth={false}
|
||||||
optionLabelProp={"name"}
|
optionLabelProp={"name"}
|
||||||
// optionFilterProp="line_desc"
|
// optionFilterProp="line_desc"
|
||||||
filterOption={(inputValue, option) => {
|
filterOption={(inputValue, option) => {
|
||||||
@@ -43,7 +45,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
|||||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||||
label: (
|
label: (
|
||||||
<div style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
<>
|
||||||
<span>
|
<span>
|
||||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||||
@@ -57,7 +59,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
|||||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -25,7 +24,6 @@ export function BillMarkExportedButton({ currentUser, bodyshop, authLevel, bill
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const [updateBill] = useMutation(gql`
|
const [updateBill] = useMutation(gql`
|
||||||
mutation UPDATE_BILL($billId: uuid!) {
|
mutation UPDATE_BILL($billId: uuid!) {
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
export default function BillPrintButton({ billid }) {
|
export default function BillPrintButton({ billid }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const Templates = TemplateList("job_special");
|
const Templates = TemplateList("job_special");
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -22,9 +20,7 @@ export default function BillPrintButton({ billid }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
"p",
|
"p"
|
||||||
null,
|
|
||||||
notification
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Warning: Error generating a document.");
|
console.warn("Warning: Error generating a document.");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -22,7 +21,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(BillMarkForReexportB
|
|||||||
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
|
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const [updateBill] = useMutation(gql`
|
const [updateBill] = useMutation(gql`
|
||||||
mutation UPDATE_BILL($billId: uuid!) {
|
mutation UPDATE_BILL($billId: uuid!) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FileAddFilled } from "@ant-design/icons";
|
import { FileAddFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Tooltip } from "antd";
|
import { Button, notification, Tooltip } from "antd";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import dayjs from "./../../utils/day";
|
import dayjs from "./../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
@@ -11,7 +11,6 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
|
|||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -26,7 +25,6 @@ export function BilllineAddInventory({ currentUser, bodyshop, billline, disabled
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { billid } = queryString.parse(useLocation().search);
|
const { billid } = queryString.parse(useLocation().search);
|
||||||
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const addToInventory = async () => {
|
const addToInventory = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
|||||||
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaTasks } from "react-icons/fa";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
@@ -14,10 +13,8 @@ import { alphaSort, dateSort } from "../../utils/sorters";
|
|||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
|
||||||
import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
|
||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
import { FaTasks } from "react-icons/fa";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -173,8 +170,6 @@ export function BillsListTableComponent({
|
|||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const hasBillsAccess = HasFeatureAccess({ bodyshop, featureName: "bills" });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("bills.labels.bills")}
|
title={t("bills.labels.bills")}
|
||||||
@@ -186,7 +181,6 @@ export function BillsListTableComponent({
|
|||||||
{job && job.converted ? (
|
{job && job.converted ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasBillsAccess}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: billsQuery.refetch },
|
actions: { refetch: billsQuery.refetch },
|
||||||
@@ -196,10 +190,9 @@ export function BillsListTableComponent({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LockerWrapperComponent featureName="bills">{t("jobs.actions.postbills")}</LockerWrapperComponent>
|
{t("jobs.actions.postbills")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasBillsAccess}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setReconciliationContext({
|
setReconciliationContext({
|
||||||
actions: { refetch: billsQuery.refetch },
|
actions: { refetch: billsQuery.refetch },
|
||||||
@@ -210,7 +203,7 @@ export function BillsListTableComponent({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LockerWrapperComponent featureName="bills"> {t("jobs.actions.reconcile")}</LockerWrapperComponent>
|
{t("jobs.actions.reconcile")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -218,7 +211,6 @@ export function BillsListTableComponent({
|
|||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
value={searchText}
|
value={searchText}
|
||||||
disabled={!hasBillsAccess}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSearchText(e.target.value);
|
setSearchText(e.target.value);
|
||||||
@@ -234,13 +226,8 @@ export function BillsListTableComponent({
|
|||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={hasBillsAccess ? filteredBills : []}
|
dataSource={filteredBills}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
locale={{
|
|
||||||
...(!hasBillsAccess && {
|
|
||||||
emptyText: <UpsellComponent upsell={upsellEnum().bills.general} />
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { selectCaBcEtfTableConvert } from "../../redux/modals/modals.selectors";
|
|||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import CaBcEtfTableModalComponent from "./ca-bc-etf-table.modal.component";
|
import CaBcEtfTableModalComponent from "./ca-bc-etf-table.modal.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
caBcEtfTableModal: selectCaBcEtfTableConvert
|
caBcEtfTableModal: selectCaBcEtfTableConvert
|
||||||
@@ -26,7 +25,6 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
|
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("ca_bc_etf_table_parse");
|
logImEXEvent("ca_bc_etf_table_parse");
|
||||||
@@ -55,9 +53,7 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
values.sendby === "email" ? "e" : "p",
|
values.sendby === "email" ? "e" : "p"
|
||||||
null,
|
|
||||||
notification
|
|
||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CopyFilled, DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Col, Form, Input, message, Row, Space, Spin, Statistic } from "antd";
|
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -14,75 +14,51 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
import { getCurrentUser } from "../../firebase/firebase.utils";
|
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
cardPaymentModal: selectCardPayment,
|
cardPaymentModal: selectCardPayment,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop
|
||||||
currentUser: getCurrentUser
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
dispatch(
|
|
||||||
insertAuditTrail({
|
|
||||||
jobid,
|
|
||||||
operation,
|
|
||||||
type
|
|
||||||
})
|
|
||||||
),
|
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||||
});
|
});
|
||||||
|
|
||||||
const CardPaymentModalComponent = ({
|
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
||||||
bodyshop,
|
|
||||||
currentUser,
|
|
||||||
cardPaymentModal,
|
|
||||||
toggleModalVisible,
|
|
||||||
insertAuditTrail
|
|
||||||
}) => {
|
|
||||||
const { context, actions } = cardPaymentModal;
|
const { context, actions } = cardPaymentModal;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [paymentLink, setPaymentLink] = useState();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const notification = useNotification();
|
|
||||||
|
|
||||||
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
||||||
variables: { jobids: [context.jobid] },
|
variables: { jobids: [context.jobid] },
|
||||||
skip: !context?.jobid
|
skip: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const collectIPayFields = () => {
|
//Initialize the intellipay window.
|
||||||
const iPayFields = document.querySelectorAll(".ipayfield");
|
|
||||||
const iPayData = {};
|
|
||||||
iPayFields.forEach((field) => {
|
|
||||||
iPayData[field.dataset.ipayname] = field.value;
|
|
||||||
});
|
|
||||||
return iPayData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SetIntellipayCallbackFunctions = () => {
|
const SetIntellipayCallbackFunctions = () => {
|
||||||
console.log("*** Set IntelliPay callback functions.");
|
console.log("*** Set IntelliPay callback functions.");
|
||||||
|
|
||||||
window.intellipay.runOnClose(() => {
|
window.intellipay.runOnClose(() => {
|
||||||
//window.intellipay.initialize();
|
//window.intellipay.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.intellipay.runOnApproval(() => {
|
window.intellipay.runOnApproval(async function (response) {
|
||||||
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
||||||
//Add a slight delay to allow the refetch to properly get the data.
|
//Add a slight delay to allow the refetch to properly get the data.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (actions?.refetch) actions.refetch();
|
if (actions && actions.refetch && typeof actions.refetch === "function")
|
||||||
|
actions.refetch();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}, 750);
|
}, 750);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.intellipay.runOnNonApproval(async (response) => {
|
window.intellipay.runOnNonApproval(async function (response) {
|
||||||
// Mutate unsuccessful payment
|
// Mutate unsuccessful payment
|
||||||
|
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
@@ -110,44 +86,36 @@ const CardPaymentModalComponent = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleIntelliPayCharge = async () => {
|
const handleIntelliPayCharge = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
//Validate
|
//Validate
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
await form.validateFields();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iPayData = collectIPayFields();
|
|
||||||
const { payments } = form.getFieldsValue();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
refresh: !!window.intellipay,
|
refresh: !!window.intellipay,
|
||||||
paymentSplitMeta: form.getFieldsValue(),
|
paymentSplitMeta: form.getFieldsValue(),
|
||||||
iPayData: iPayData,
|
|
||||||
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email }))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
eval(response.data);
|
eval(response.data);
|
||||||
pollForIntelliPay(() => {
|
SetIntellipayCallbackFunctions();
|
||||||
SetIntellipayCallbackFunctions();
|
window.intellipay.autoOpen();
|
||||||
window.intellipay.autoOpen();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const rg = document.createRange();
|
var rg = document.createRange();
|
||||||
const node = rg.createContextualFragment(response.data);
|
let node = rg.createContextualFragment(response.data);
|
||||||
document.documentElement.appendChild(node);
|
document.documentElement.appendChild(node);
|
||||||
pollForIntelliPay(() => {
|
SetIntellipayCallbackFunctions();
|
||||||
SetIntellipayCallbackFunctions();
|
window.intellipay.isAutoOpen = true;
|
||||||
window.intellipay.isAutoOpen = true;
|
window.intellipay.initialize();
|
||||||
window.intellipay.initialize();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.open({
|
notification.open({
|
||||||
@@ -158,44 +126,6 @@ const CardPaymentModalComponent = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIntelliPayChargeShortLink = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
//Validate
|
|
||||||
try {
|
|
||||||
await form.validateFields();
|
|
||||||
} catch {
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const iPayData = collectIPayFields();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { payments } = form.getFieldsValue();
|
|
||||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
|
||||||
bodyshop,
|
|
||||||
amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
|
|
||||||
account: payments && data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
|
||||||
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
|
||||||
paymentSplitMeta: form.getFieldsValue(),
|
|
||||||
iPayData: iPayData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response?.data?.shorUrl) {
|
|
||||||
setPaymentLink(response.data.shorUrl);
|
|
||||||
await navigator.clipboard.writeText(response.data.shorUrl);
|
|
||||||
message.success(t("general.actions.copied"));
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
notification.open({
|
|
||||||
type: "error",
|
|
||||||
message: t("job_payments.notifications.error.openingip")
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title="Card Payment">
|
<Card title="Card Payment">
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
@@ -207,56 +137,81 @@ const CardPaymentModalComponent = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.List name={["payments"]}>
|
<Form.List name={["payments"]}>
|
||||||
{(fields, { add, remove }) => (
|
{(fields, { add, remove, move }) => {
|
||||||
<div>
|
return (
|
||||||
{fields.map((field, index) => (
|
<div>
|
||||||
<Form.Item key={field.key}>
|
{fields.map((field, index) => (
|
||||||
<Row gutter={[16, 16]}>
|
<Form.Item key={field.key}>
|
||||||
<Col span={16}>
|
<Row gutter={[16, 16]}>
|
||||||
<Form.Item
|
<Col span={16}>
|
||||||
key={`${index}jobid`}
|
<Form.Item
|
||||||
label={t("jobs.fields.ro_number")}
|
key={`${index}jobid`}
|
||||||
name={[field.name, "jobid"]}
|
label={t("jobs.fields.ro_number")}
|
||||||
rules={[{ required: true }]}
|
name={[field.name, "jobid"]}
|
||||||
>
|
rules={[
|
||||||
<JobSearchSelectComponent notExported={false} clm_no />
|
{
|
||||||
</Form.Item>
|
required: true
|
||||||
</Col>
|
//message: t("general.validation.required"),
|
||||||
<Col span={6}>
|
}
|
||||||
<Form.Item
|
]}
|
||||||
key={`${index}amount`}
|
>
|
||||||
label={t("payments.fields.amount")}
|
<JobSearchSelectComponent notExported={false} clm_no />
|
||||||
name={[field.name, "amount"]}
|
</Form.Item>
|
||||||
rules={[{ required: true }]}
|
</Col>
|
||||||
>
|
<Col span={6}>
|
||||||
<CurrencyFormItemComponent />
|
<Form.Item
|
||||||
</Form.Item>
|
key={`${index}amount`}
|
||||||
</Col>
|
label={t("payments.fields.amount")}
|
||||||
<Col span={2}>
|
name={[field.name, "amount"]}
|
||||||
<DeleteFilled style={{ margin: "1rem" }} onClick={() => remove(field.name)} />
|
rules={[
|
||||||
</Col>
|
{
|
||||||
</Row>
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CurrencyFormItemComponent />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<DeleteFilled
|
||||||
|
style={{ margin: "1rem" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add();
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{t("general.actions.add")}
|
||||||
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
))}
|
</div>
|
||||||
<Form.Item>
|
);
|
||||||
<Button type="dashed" onClick={() => add()} style={{ width: "100%" }}>
|
}}
|
||||||
{t("general.actions.add")}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
shouldUpdate={(prevValues, curValues) =>
|
||||||
prevValues.payments?.map((p) => p?.jobid + p?.amount).join() !==
|
prevValues.payments?.map((p) => p?.jobid).join() !== curValues.payments?.map((p) => p?.jobid).join()
|
||||||
curValues.payments?.map((p) => p?.jobid + p?.amount).join()
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
|
if (
|
||||||
|
payments?.length > 0 &&
|
||||||
|
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||||
|
) {
|
||||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -288,7 +243,10 @@ const CardPaymentModalComponent = ({
|
|||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0);
|
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
||||||
|
return acc + (val?.amount || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space style={{ float: "right" }}>
|
<Space style={{ float: "right" }}>
|
||||||
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
||||||
@@ -315,63 +273,14 @@ const CardPaymentModalComponent = ({
|
|||||||
>
|
>
|
||||||
{t("job_payments.buttons.proceedtopayment")}
|
{t("job_payments.buttons.proceedtopayment")}
|
||||||
</Button>
|
</Button>
|
||||||
<Space direction="vertical" align="center">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
// data-ipayname="submit"
|
|
||||||
className="ipayfield"
|
|
||||||
loading={queryLoading || loading}
|
|
||||||
disabled={!(totalAmountToCharge > 0)}
|
|
||||||
onClick={handleIntelliPayChargeShortLink}
|
|
||||||
>
|
|
||||||
{t("job_payments.buttons.create_short_link")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
{paymentLink && (
|
|
||||||
<Space
|
|
||||||
style={{ cursor: "pointer", float: "right" }}
|
|
||||||
align="end"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(paymentLink);
|
|
||||||
message.success(t("general.actions.copied"));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>{paymentLink}</div>
|
|
||||||
<CopyFilled />
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</Spin>
|
</Spin>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent);
|
||||||
|
|
||||||
//Poll for window.IntelliPay.fixAmount for 5 seconds. If it doesn't come up, just try anyways to force the possible error.
|
|
||||||
function pollForIntelliPay(callbackFunction) {
|
|
||||||
const timeout = 5000;
|
|
||||||
const interval = 150; // Poll every 100 milliseconds
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
function checkFixAmount() {
|
|
||||||
if (window.intellipay && window.intellipay.fixAmount !== undefined) {
|
|
||||||
callbackFunction();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Date.now() - startTime >= timeout) {
|
|
||||||
console.log("Stopped polling IntelliPay after 10 seconds. Attemping to set functions anyways.");
|
|
||||||
callbackFunction();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkFixAmount, interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFixAmount();
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CardPaymentModalComponent from "./card-payment-modal.component";
|
import CardPaymentModalComponent from "./card-payment-modal.component.";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
cardPaymentModal: selectCardPayment,
|
cardPaymentModal: selectCardPayment,
|
||||||
@@ -1,50 +1,89 @@
|
|||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { getToken } from "@firebase/messaging";
|
import { getToken, onMessage } from "@firebase/messaging";
|
||||||
|
import { Button, notification, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useContext, useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext";
|
|
||||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||||
|
import FcmHandler from "../../utils/fcm-handler";
|
||||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||||
import "./chat-affix.styles.scss";
|
import "./chat-affix.styles.scss";
|
||||||
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
|
||||||
|
|
||||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { socket } = useContext(SocketContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!bodyshop || !bodyshop.messagingservicesid) return;
|
if (!bodyshop || !bodyshop.messagingservicesid) return;
|
||||||
|
|
||||||
async function SubscribeToTopicForFCMNotification() {
|
async function SubscribeToTopic() {
|
||||||
try {
|
try {
|
||||||
await requestForToken();
|
const r = await axios.post("/notifications/subscribe", {
|
||||||
await axios.post("/notifications/subscribe", {
|
|
||||||
fcm_tokens: await getToken(messaging, {
|
fcm_tokens: await getToken(messaging, {
|
||||||
vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY
|
vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY
|
||||||
}),
|
}),
|
||||||
type: "messaging",
|
type: "messaging",
|
||||||
imexshopid: bodyshop.imexshopid
|
imexshopid: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
console.log("FCM Topic Subscription", r.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error attempting to subscribe to messaging topic: ", error);
|
console.log("Error attempting to subscribe to messaging topic: ", error);
|
||||||
|
notification.open({
|
||||||
|
key: "fcm",
|
||||||
|
type: "warning",
|
||||||
|
message: t("general.errors.fcm"),
|
||||||
|
btn: (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await requestForToken();
|
||||||
|
SubscribeToTopic();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.tryagain")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const win = window.open(
|
||||||
|
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
win.focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.labels.help")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubscribeToTopicForFCMNotification();
|
SubscribeToTopic();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [bodyshop]);
|
||||||
|
|
||||||
//Register WS handlers
|
useEffect(() => {
|
||||||
if (socket && socket.connected) {
|
function handleMessage(payload) {
|
||||||
registerMessagingHandlers({ socket, client });
|
FcmHandler({
|
||||||
|
client,
|
||||||
|
payload: (payload && payload.data && payload.data.data) || payload.data
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stopMessageListener, channel;
|
||||||
|
try {
|
||||||
|
stopMessageListener = onMessage(messaging, handleMessage);
|
||||||
|
channel = new BroadcastChannel("imex-sw-messages");
|
||||||
|
channel.addEventListener("message", handleMessage);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Unable to set event listeners.");
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (socket && socket.connected) {
|
stopMessageListener && stopMessageListener();
|
||||||
unregisterMessagingHandlers({ socket });
|
channel && channel.removeEventListener("message", handleMessage);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [bodyshop, socket, t, client]);
|
}, [client]);
|
||||||
|
|
||||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,434 +0,0 @@
|
|||||||
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
|
||||||
import { gql } from "@apollo/client";
|
|
||||||
|
|
||||||
const logLocal = (message, ...args) => {
|
|
||||||
if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
|
|
||||||
console.log(`==================== ${message} ====================`);
|
|
||||||
console.dir({ ...args });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utility function to enrich conversation data
|
|
||||||
const enrichConversation = (conversation, isOutbound) => ({
|
|
||||||
...conversation,
|
|
||||||
updated_at: conversation.updated_at || new Date().toISOString(),
|
|
||||||
unreadcnt: conversation.unreadcnt || 0,
|
|
||||||
archived: conversation.archived || false,
|
|
||||||
label: conversation.label || null,
|
|
||||||
job_conversations: conversation.job_conversations || [],
|
|
||||||
messages_aggregate: conversation.messages_aggregate || {
|
|
||||||
__typename: "messages_aggregate",
|
|
||||||
aggregate: {
|
|
||||||
__typename: "messages_aggregate_fields",
|
|
||||||
count: isOutbound ? 0 : 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
__typename: "conversations"
|
|
||||||
});
|
|
||||||
|
|
||||||
export const registerMessagingHandlers = ({ socket, client }) => {
|
|
||||||
if (!(socket && client)) return;
|
|
||||||
|
|
||||||
const handleNewMessageSummary = async (message) => {
|
|
||||||
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
|
||||||
|
|
||||||
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
|
||||||
|
|
||||||
const queryVariables = { offset: 0 };
|
|
||||||
|
|
||||||
if (!existingConversation && conversationId) {
|
|
||||||
// Attempt to read from the cache to determine if this is actually a new conversation
|
|
||||||
try {
|
|
||||||
const cachedConversation = client.cache.readFragment({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fragment: gql`
|
|
||||||
fragment ExistingConversationCheck on conversations {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cachedConversation) {
|
|
||||||
logLocal("handleNewMessageSummary - Existing Conversation inferred from cache", {
|
|
||||||
conversationId
|
|
||||||
});
|
|
||||||
return handleNewMessageSummary({
|
|
||||||
...message,
|
|
||||||
existingConversation: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle new conversation
|
|
||||||
if (!existingConversation && newConversation?.phone_num) {
|
|
||||||
logLocal("handleNewMessageSummary - New Conversation", newConversation);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const queryResults = client.cache.readQuery({
|
|
||||||
query: CONVERSATION_LIST_QUERY,
|
|
||||||
variables: queryVariables
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingConversations = queryResults?.conversations || [];
|
|
||||||
const enrichedConversation = enrichConversation(newConversation, isoutbound);
|
|
||||||
|
|
||||||
if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
|
|
||||||
client.cache.modify({
|
|
||||||
id: "ROOT_QUERY",
|
|
||||||
fields: {
|
|
||||||
conversations(existingConversations = []) {
|
|
||||||
return [enrichedConversation, ...existingConversations];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating cache for new conversation:", error);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle existing conversation
|
|
||||||
if (existingConversation) {
|
|
||||||
try {
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
updated_at: () => new Date().toISOString(),
|
|
||||||
archived: () => false,
|
|
||||||
messages_aggregate(cached = { aggregate: { count: 0 } }) {
|
|
||||||
const currentCount = cached.aggregate?.count || 0;
|
|
||||||
if (!isoutbound) {
|
|
||||||
return {
|
|
||||||
__typename: "messages_aggregate",
|
|
||||||
aggregate: {
|
|
||||||
__typename: "messages_aggregate_fields",
|
|
||||||
count: currentCount + 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating cache for existing conversation:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logLocal("New Conversation Summary finished without work", { message });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNewMessageDetailed = (message) => {
|
|
||||||
const { conversationId, newMessage } = message;
|
|
||||||
|
|
||||||
logLocal("handleNewMessageDetailed - Start", message);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if the conversation exists in the cache
|
|
||||||
const queryResults = client.cache.readQuery({
|
|
||||||
query: GET_CONVERSATION_DETAILS,
|
|
||||||
variables: { conversationId }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!queryResults?.conversations_by_pk) {
|
|
||||||
console.warn("Conversation not found in cache:", { conversationId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the new message to the conversation's message list using cache.modify
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
messages(existingMessages = []) {
|
|
||||||
return [...existingMessages, newMessage];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logLocal("handleNewMessageDetailed - Message appended successfully", {
|
|
||||||
conversationId,
|
|
||||||
newMessage
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating conversation messages in cache:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMessageChanged = (message) => {
|
|
||||||
if (!message) {
|
|
||||||
logLocal("handleMessageChanged - No message provided", message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logLocal("handleMessageChanged - Start", message);
|
|
||||||
|
|
||||||
try {
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
|
|
||||||
fields: {
|
|
||||||
messages(existingMessages = [], { readField }) {
|
|
||||||
return existingMessages.map((messageRef) => {
|
|
||||||
// Check if this is the message to update
|
|
||||||
if (readField("id", messageRef) === message.id) {
|
|
||||||
const currentStatus = readField("status", messageRef);
|
|
||||||
|
|
||||||
// Handle known types of message changes
|
|
||||||
switch (message.type) {
|
|
||||||
case "status-changed":
|
|
||||||
// Prevent overwriting if the current status is already "delivered"
|
|
||||||
if (currentStatus === "delivered") {
|
|
||||||
logLocal("handleMessageChanged - Status already delivered, skipping update", {
|
|
||||||
messageId: message.id
|
|
||||||
});
|
|
||||||
return messageRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status field
|
|
||||||
return {
|
|
||||||
...messageRef,
|
|
||||||
status: message.status
|
|
||||||
};
|
|
||||||
|
|
||||||
case "text-updated":
|
|
||||||
// Handle changes to the message text
|
|
||||||
return {
|
|
||||||
...messageRef,
|
|
||||||
text: message.text
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add cases for other known message types as needed
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Log a warning for unhandled message types
|
|
||||||
logLocal("handleMessageChanged - Unhandled message type", { type: message.type });
|
|
||||||
return messageRef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageRef; // Keep other messages unchanged
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logLocal("handleMessageChanged - Message updated successfully", {
|
|
||||||
messageId: message.id,
|
|
||||||
type: message.type
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("handleMessageChanged - Error modifying cache:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConversationChanged = async (data) => {
|
|
||||||
if (!data) {
|
|
||||||
logLocal("handleConversationChanged - No data provided", data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { conversationId, type, job_conversations, messageIds, ...fields } = data;
|
|
||||||
logLocal("handleConversationChanged - Start", data);
|
|
||||||
|
|
||||||
const updatedAt = new Date().toISOString();
|
|
||||||
|
|
||||||
const updateConversationList = (newConversation) => {
|
|
||||||
try {
|
|
||||||
const existingList = client.cache.readQuery({
|
|
||||||
query: CONVERSATION_LIST_QUERY,
|
|
||||||
variables: { offset: 0 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedList = existingList?.conversations
|
|
||||||
? [
|
|
||||||
newConversation,
|
|
||||||
...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates
|
|
||||||
]
|
|
||||||
: [newConversation];
|
|
||||||
|
|
||||||
client.cache.writeQuery({
|
|
||||||
query: CONVERSATION_LIST_QUERY,
|
|
||||||
variables: { offset: 0 },
|
|
||||||
data: {
|
|
||||||
conversations: updatedList
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logLocal("handleConversationChanged - Conversation list updated successfully", newConversation);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating conversation list in the cache:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle specific types
|
|
||||||
try {
|
|
||||||
switch (type) {
|
|
||||||
case "conversation-marked-read":
|
|
||||||
if (conversationId && messageIds?.length > 0) {
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
messages(existingMessages = [], { readField }) {
|
|
||||||
return existingMessages.map((message) => {
|
|
||||||
if (messageIds.includes(readField("id", message))) {
|
|
||||||
return { ...message, read: true };
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
messages_aggregate: () => ({
|
|
||||||
__typename: "messages_aggregate",
|
|
||||||
aggregate: { __typename: "messages_aggregate_fields", count: 0 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "conversation-created":
|
|
||||||
updateConversationList({ ...fields, job_conversations, updated_at: updatedAt });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "conversation-unarchived":
|
|
||||||
case "conversation-archived":
|
|
||||||
// Would like to someday figure out how to get this working without refetch queries,
|
|
||||||
// But I have but a solid 4 hours into it, and there are just too many weird occurrences
|
|
||||||
try {
|
|
||||||
const listQueryVariables = { offset: 0 };
|
|
||||||
const detailsQueryVariables = { conversationId };
|
|
||||||
|
|
||||||
// Check if conversation details exist in the cache
|
|
||||||
const detailsExist = !!client.cache.readQuery({
|
|
||||||
query: GET_CONVERSATION_DETAILS,
|
|
||||||
variables: detailsQueryVariables
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refetch conversation list
|
|
||||||
await client.refetchQueries({
|
|
||||||
include: [CONVERSATION_LIST_QUERY, ...(detailsExist ? [GET_CONVERSATION_DETAILS] : [])],
|
|
||||||
variables: [
|
|
||||||
{ query: CONVERSATION_LIST_QUERY, variables: listQueryVariables },
|
|
||||||
...(detailsExist
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
query: GET_CONVERSATION_DETAILS,
|
|
||||||
variables: detailsQueryVariables
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
logLocal("handleConversationChanged - Refetched queries after state change", {
|
|
||||||
conversationId,
|
|
||||||
type
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error refetching queries after conversation state change:", error);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tag-added":
|
|
||||||
// Ensure `job_conversations` is properly formatted
|
|
||||||
const formattedJobConversations = job_conversations.map((jc) => ({
|
|
||||||
__typename: "job_conversations",
|
|
||||||
jobid: jc.jobid || jc.job?.id,
|
|
||||||
conversationid: conversationId,
|
|
||||||
job: jc.job || {
|
|
||||||
__typename: "jobs",
|
|
||||||
id: data.selectedJob.id,
|
|
||||||
ro_number: data.selectedJob.ro_number,
|
|
||||||
ownr_co_nm: data.selectedJob.ownr_co_nm,
|
|
||||||
ownr_fn: data.selectedJob.ownr_fn,
|
|
||||||
ownr_ln: data.selectedJob.ownr_ln
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
job_conversations: (existing = []) => {
|
|
||||||
// Ensure no duplicates based on both `conversationid` and `jobid`
|
|
||||||
const existingLinks = new Set(
|
|
||||||
existing.map((jc) => {
|
|
||||||
const jobId = client.cache.readFragment({
|
|
||||||
id: client.cache.identify(jc),
|
|
||||||
fragment: gql`
|
|
||||||
fragment JobConversationLinkAdded on job_conversations {
|
|
||||||
jobid
|
|
||||||
conversationid
|
|
||||||
}
|
|
||||||
`
|
|
||||||
})?.jobid;
|
|
||||||
return `${jobId}:${conversationId}`; // Unique identifier for a job-conversation link
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const newItems = formattedJobConversations.filter((jc) => {
|
|
||||||
const uniqueLink = `${jc.jobid}:${jc.conversationid}`;
|
|
||||||
return !existingLinks.has(uniqueLink);
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...existing, ...newItems];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tag-removed":
|
|
||||||
try {
|
|
||||||
const conversationCacheId = client.cache.identify({ __typename: "conversations", id: conversationId });
|
|
||||||
|
|
||||||
// Evict the specific cache entry for job_conversations
|
|
||||||
client.cache.evict({
|
|
||||||
id: conversationCacheId,
|
|
||||||
fieldName: "job_conversations"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Garbage collect evicted entries
|
|
||||||
client.cache.gc();
|
|
||||||
|
|
||||||
logLocal("handleConversationChanged - tag removed - Refetched conversation list after state change", {
|
|
||||||
conversationId,
|
|
||||||
type
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error refetching queries after conversation state change: (Tag Removed)", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logLocal("handleConversationChanged - Unhandled type", { type });
|
|
||||||
client.cache.modify({
|
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
||||||
fields: {
|
|
||||||
...Object.fromEntries(
|
|
||||||
Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error handling conversation changes:", { type, error });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.on("new-message-summary", handleNewMessageSummary);
|
|
||||||
socket.on("new-message-detailed", handleNewMessageDetailed);
|
|
||||||
socket.on("message-changed", handleMessageChanged);
|
|
||||||
socket.on("conversation-changed", handleConversationChanged);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unregisterMessagingHandlers = ({ socket }) => {
|
|
||||||
if (!socket) return;
|
|
||||||
socket.off("new-message-summary");
|
|
||||||
socket.off("new-message-detailed");
|
|
||||||
socket.off("message-changed");
|
|
||||||
socket.off("conversation-changed");
|
|
||||||
};
|
|
||||||
@@ -1,49 +1,27 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
export default function ChatArchiveButton({ conversation }) {
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
|
||||||
|
|
||||||
export function ChatArchiveButton({ conversation, bodyshop }) {
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
||||||
const { socket } = useContext(SocketContext);
|
|
||||||
|
|
||||||
const handleToggleArchive = async () => {
|
const handleToggleArchive = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const updatedConversation = await updateConversation({
|
await updateConversation({
|
||||||
variables: { id: conversation.id, archived: !conversation.archived }
|
variables: { id: conversation.id, archived: !conversation.archived },
|
||||||
|
refetchQueries: ["CONVERSATION_LIST_QUERY"]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
socket.emit("conversation-modified", {
|
|
||||||
type: "conversation-archived",
|
|
||||||
conversationId: conversation.id,
|
|
||||||
bodyshopId: bodyshop.id,
|
|
||||||
archived: updatedConversation.data.update_conversations_by_pk.archived
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleToggleArchive} loading={loading} className="archive-button" type="primary">
|
<Button onClick={handleToggleArchive} loading={loading} type="primary">
|
||||||
{conversation.archived ? t("messaging.labels.unarchive") : t("messaging.labels.archive")}
|
{conversation.archived ? t("messaging.labels.unarchive") : t("messaging.labels.archive")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatArchiveButton);
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Badge, Card, List, Space, Tag } from "antd";
|
import { Badge, Card, List, Space, Tag } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList } from "react-virtualized";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
@@ -19,26 +19,19 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId))
|
setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId))
|
||||||
});
|
});
|
||||||
|
|
||||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) {
|
function ChatConversationListComponent({
|
||||||
// That comma is there for a reason, do not remove it
|
conversationList,
|
||||||
const [, forceUpdate] = useState(false);
|
selectedConversation,
|
||||||
|
setSelectedConversation,
|
||||||
|
loadMoreConversations
|
||||||
|
}) {
|
||||||
|
const cache = new CellMeasurerCache({
|
||||||
|
fixedWidth: true,
|
||||||
|
defaultHeight: 60
|
||||||
|
});
|
||||||
|
|
||||||
// Re-render every minute
|
const rowRenderer = ({ index, key, style, parent }) => {
|
||||||
useEffect(() => {
|
const item = conversationList[index];
|
||||||
const interval = setInterval(() => {
|
|
||||||
forceUpdate((prev) => !prev); // Toggle state to trigger re-render
|
|
||||||
}, 60000); // 1 minute in milliseconds
|
|
||||||
|
|
||||||
return () => clearInterval(interval); // Cleanup on unmount
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Memoize the sorted conversation list
|
|
||||||
const sortedConversationList = React.useMemo(() => {
|
|
||||||
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
|
|
||||||
}, [conversationList]);
|
|
||||||
|
|
||||||
const renderConversation = (index) => {
|
|
||||||
const item = sortedConversationList[index];
|
|
||||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||||
const cardContentLeft =
|
const cardContentLeft =
|
||||||
item.job_conversations.length > 0
|
item.job_conversations.length > 0
|
||||||
@@ -59,8 +52,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0} />;
|
||||||
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count} />;
|
|
||||||
|
|
||||||
const getCardStyle = () =>
|
const getCardStyle = () =>
|
||||||
item.id === selectedConversation
|
item.id === selectedConversation
|
||||||
@@ -68,26 +60,40 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<CellMeasurer key={key} cache={cache} parent={parent} columnIndex={0} rowIndex={index}>
|
||||||
key={item.id}
|
<List.Item
|
||||||
onClick={() => setSelectedConversation(item.id)}
|
onClick={() => setSelectedConversation(item.id)}
|
||||||
className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
|
style={style}
|
||||||
>
|
className={`chat-list-item
|
||||||
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
|
${item.id === selectedConversation ? "chat-list-selected-conversation" : null}`}
|
||||||
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
|
>
|
||||||
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
|
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
|
||||||
</Card>
|
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
|
||||||
</List.Item>
|
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
|
||||||
|
</Card>
|
||||||
|
</List.Item>
|
||||||
|
</CellMeasurer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-list-container">
|
<div className="chat-list-container">
|
||||||
<Virtuoso
|
<AutoSizer>
|
||||||
data={sortedConversationList}
|
{({ height, width }) => (
|
||||||
itemContent={(index) => renderConversation(index)}
|
<VirtualizedList
|
||||||
style={{ height: "100%", width: "100%" }}
|
height={height}
|
||||||
/>
|
width={width}
|
||||||
|
rowCount={conversationList.length}
|
||||||
|
rowHeight={cache.rowHeight}
|
||||||
|
rowRenderer={rowRenderer}
|
||||||
|
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||||
|
if (scrollTop + clientHeight === scrollHeight) {
|
||||||
|
loadMoreConversations();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.chat-list-container {
|
.chat-list-container {
|
||||||
height: 100%; /* Ensure it takes up the full available height */
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
border: 1px solid gainsboro;
|
border: 1px solid gainsboro;
|
||||||
overflow: auto; /* Allow scrolling for the Virtuoso component */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-item {
|
.chat-list-item {
|
||||||
@@ -14,24 +14,3 @@
|
|||||||
color: #ff7a00;
|
color: #ff7a00;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Virtuoso item container adjustments */
|
|
||||||
.chat-list-container > div {
|
|
||||||
height: 100%; /* Ensure Virtuoso takes full height */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add spacing and better alignment for items */
|
|
||||||
.chat-list-item {
|
|
||||||
padding: 0.5rem 0; /* Add spacing between list items */
|
|
||||||
|
|
||||||
.ant-card {
|
|
||||||
border-radius: 8px; /* Slight rounding for card edges */
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow for better definition */
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .ant-card {
|
|
||||||
border-color: #ff7a00; /* Highlight border on hover */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Tag } from "antd";
|
import { Tag } from "antd";
|
||||||
import React, { useContext } from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
export default function ChatConversationTitleTags({ jobConversations }) {
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
|
||||||
|
|
||||||
export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
|
||||||
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
|
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
|
||||||
const { socket } = useContext(SocketContext);
|
|
||||||
|
|
||||||
const handleRemoveTag = async (jobId) => {
|
const handleRemoveTag = (jobId) => {
|
||||||
const convId = jobConversations[0].conversationid;
|
const convId = jobConversations[0].conversationid;
|
||||||
if (!!convId) {
|
if (!!convId) {
|
||||||
await removeJobConversation({
|
removeJobConversation({
|
||||||
variables: {
|
variables: {
|
||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
jobId: jobId
|
jobId: jobId
|
||||||
@@ -39,17 +28,6 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
// Emit the `conversation-modified` event
|
|
||||||
socket.emit("conversation-modified", {
|
|
||||||
bodyshopId: bodyshop.id,
|
|
||||||
conversationId: convId,
|
|
||||||
type: "tag-removed",
|
|
||||||
jobId: jobId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logImEXEvent("messaging_remove_job_tag", {
|
logImEXEvent("messaging_remove_job_tag", {
|
||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
jobId: jobId
|
jobId: jobId
|
||||||
@@ -76,5 +54,3 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitleTags);
|
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conv
|
|||||||
import ChatLabelComponent from "../chat-label/chat-label.component";
|
import ChatLabelComponent from "../chat-label/chat-label.component";
|
||||||
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
||||||
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
export default function ChatConversationTitle({ conversation }) {
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
|
||||||
|
|
||||||
export function ChatConversationTitle({ conversation }) {
|
|
||||||
return (
|
return (
|
||||||
<Space className="chat-title" wrap>
|
<Space wrap>
|
||||||
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
||||||
<ChatLabelComponent conversation={conversation} />
|
<ChatLabelComponent conversation={conversation} />
|
||||||
<ChatPrintButton conversation={conversation} />
|
<ChatPrintButton conversation={conversation} />
|
||||||
@@ -25,5 +19,3 @@ export function ChatConversationTitle({ conversation }) {
|
|||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle);
|
|
||||||
|
|||||||
@@ -5,26 +5,10 @@ import ChatMessageListComponent from "../chat-messages-list/chat-message-list.co
|
|||||||
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||||
import "./chat-conversation.styles.scss";
|
import "./chat-conversation.styles.scss";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
export default function ChatConversationComponent({ subState, conversation, messages, handleMarkConversationAsRead }) {
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = () => ({});
|
|
||||||
|
|
||||||
export function ChatConversationComponent({
|
|
||||||
subState,
|
|
||||||
conversation,
|
|
||||||
messages,
|
|
||||||
handleMarkConversationAsRead,
|
|
||||||
bodyshop
|
|
||||||
}) {
|
|
||||||
const [loading, error] = subState;
|
const [loading, error] = subState;
|
||||||
|
|
||||||
if (conversation?.archived) return null;
|
|
||||||
if (loading) return <LoadingSkeleton />;
|
if (loading) return <LoadingSkeleton />;
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
@@ -34,11 +18,9 @@ export function ChatConversationComponent({
|
|||||||
onMouseDown={handleMarkConversationAsRead}
|
onMouseDown={handleMarkConversationAsRead}
|
||||||
onKeyDown={handleMarkConversationAsRead}
|
onKeyDown={handleMarkConversationAsRead}
|
||||||
>
|
>
|
||||||
<ChatConversationTitle conversation={conversation} bodyshop={bodyshop} />
|
<ChatConversationTitle conversation={conversation} />
|
||||||
<ChatMessageListComponent messages={messages} />
|
<ChatMessageListComponent messages={messages} />
|
||||||
<ChatSendMessage conversation={conversation} />
|
<ChatSendMessage conversation={conversation} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationComponent);
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user