Compare commits

..
86 Commits
Author SHA1 Message Date
Maksim Eltyshev 236c1020b3 chore: Update version 2022-09-03 22:48:19 +05:00
Maksim Eltyshev 3df07c10fa fix: Use password strength estimator
Closes #294
2022-09-03 22:47:06 +05:00
Steven Correia 543a992d98 fix: Fix proxy forwarding (#295) 2022-09-03 20:00:23 +05:00
Maksim Eltyshev 9cde4e63f9 chore: Update version 2022-09-02 18:03:44 +05:00
Maksim Eltyshev 95738ee7d3 fix: Add missing validation for password 2022-09-02 17:59:12 +05:00
Maksim Eltyshev bb7cdf5dd4 chore: Update version 2022-09-02 17:05:48 +05:00
Maksim Eltyshev 253af55790 fix: Add password requirements when creating user
Closes #293
2022-09-02 17:04:51 +05:00
Maksim Eltyshev 62c3cca9a7 fix: Fix password length validation 2022-08-30 15:13:17 +02:00
Maksim Eltyshev b476a19b79 chore: Update version 2022-08-30 13:50:45 +02:00
Maksim Eltyshev 3379c65a87 fix: Prevent tabnabbing in markdown 2022-08-30 13:47:50 +02:00
Maksim Eltyshev 1a6fc40804 chore: Update version 2022-08-26 19:13:10 +02:00
Maksim Eltyshev 42092efcd2 fix: Remove dots in translations 2022-08-26 19:11:32 +02:00
Maksim Eltyshev 5c91bddfe7 feat: Stronger password policy 2022-08-26 18:59:44 +02:00
Maksim Eltyshev fd8f40f1b0 chore: Update version 2022-08-26 16:05:47 +02:00
Maksim Eltyshev 5b64d465e2 fix: Use move-file instead of rename 2022-08-26 16:02:47 +02:00
Maksim Eltyshev 212fb8ac8c fix: Add missing border radius 2022-08-26 02:47:43 +02:00
Maksim Eltyshev cece2254d7 fix: Change mechanics of file uploading 2022-08-26 02:45:27 +02:00
Maksim Eltyshev c940805f14 chore: Fix version 2022-08-24 15:44:01 +02:00
Maksim Eltyshev 052ab7f653 chore: Update version 2022-08-24 15:37:27 +02:00
Maksim Eltyshev 5a38805588 feat: Add Italian translation. Thanks to @belf88 2022-08-24 15:27:12 +02:00
Maksim Eltyshev 2dbbead2e9 fix: Add missing border radiuses 2022-08-23 21:19:43 +02:00
Maksim Eltyshev 039eee0aac ref: Little refactoring 2022-08-23 21:06:50 +02:00
Maksim Eltyshev 07867fc0b2 fix: Use custom logger only for production 2022-08-23 20:45:42 +02:00
Maksim Eltyshev 55d07b4560 meta: Fixes in readme 2022-08-23 01:14:11 +02:00
Maksim Eltyshev 289d2bf5ae meta: Fix ignore files for logs 2022-08-23 00:51:33 +02:00
Steven Correia 6429e22d59 feat: Modify logger to log to file that supports fail2ban (#284) 2022-08-23 03:42:56 +05:00
Maksim Eltyshev 21ef850913 fix: Fix missed function name 2022-08-22 16:27:14 +02:00
Maksim Eltyshev e4769220da chore: Update version 2022-08-19 14:04:26 +02:00
Maksim Eltyshev 378e02c0a4 Merge branch 'master' of https://github.com/plankanban/planka 2022-08-19 14:01:24 +02:00
Maksim Eltyshev 51fa7df69c feat: Permissions for board members
Closes #262
2022-08-19 14:00:40 +02:00
Maksim Eltyshev 68806b9080 meta: Add sponsor button 2022-08-18 16:51:59 +05:00
Maksim Eltyshev d80a538857 fix: Little improvements in users modal 2022-08-17 02:00:24 +02:00
Maksim Eltyshev 78669da01d feat: Add search by users, members and labels 2022-08-17 00:53:08 +02:00
Maksim Eltyshev 3a7720974b chore: Update version 2022-08-09 22:34:16 +02:00
Maksim Eltyshev eea94e0ee2 ref: Little change for consistency 2022-08-09 22:31:43 +02:00
Maksim Eltyshev 4555dafd75 meta: Add vscode config 2022-08-09 19:36:08 +02:00
Maksim Eltyshev 95ebb27042 ref: Prettify env sample 2022-08-09 19:17:19 +02:00
Maksim Eltyshev d38e555293 fix: Provide default value for token expiration 2022-08-09 19:09:07 +02:00
SimonTagne 7786533a90 feat: Improve security of access tokens (#279)
Closes #275
2022-08-09 21:03:21 +05:00
SimonTagne dab38cbc18 ref: Ignore public js linting (#278) 2022-08-08 17:58:41 +05:00
Maksim Eltyshev 3714bbc06f ref: Refactoring 2022-08-04 13:31:14 +02:00
Maksim Eltyshev 1f569bdb61 chore: Update version 2022-08-04 00:41:57 +02:00
Maksim Eltyshev ac1df5201d fix: Fix path traversal vulnerability 2022-08-04 00:37:30 +02:00
Maksim Eltyshev fbe24c0fc0 chore: Update version 2022-08-03 13:28:41 +02:00
Maksim Eltyshev 79daed6afa docs: Add security policy
Closes #276
2022-08-03 16:15:40 +05:00
Maksim Eltyshev a9111bf7ba feat: Add Korean translation. Thanks to @chooya 2022-08-03 12:21:01 +02:00
Maksim Eltyshev 2d50211892 fix: Fix label style 2022-08-01 00:18:22 +02:00
Maksim Eltyshev 139971eca9 chore: Update version 2022-07-29 17:50:11 +02:00
Maksim Eltyshev 34db8947b6 feat: Toggle actions details, little redesign 2022-07-29 17:40:18 +02:00
Maksim Eltyshev 82e4f73c1f ref: Fix variable names 2022-07-29 17:11:38 +02:00
Maksim Eltyshev 560bddbe18 fix: Fix styles 2022-07-26 13:53:30 +02:00
Maksim Eltyshev 9c192f4b5c fix: Fix select behaviour 2022-07-26 12:49:40 +02:00
Maksim Eltyshev a148a3aae5 feat: Add language selector
Closes #212
2022-07-26 12:26:42 +02:00
Maksim Eltyshev 7c1b885d2a chore: Update version 2022-07-24 17:17:59 +02:00
Maksim Eltyshev a3f33d0361 fix: Fix core locale loading 2022-07-24 17:13:28 +02:00
Maksim Eltyshev fe62180cda chore: Update dependencies 2022-07-24 17:04:26 +02:00
Maksim Eltyshev 58abd0691f chore: Update version 2022-07-21 11:42:56 +02:00
Maksim Eltyshev ad67b9ba24 feat: Add tasks reordering
Closes #50, closes #232
2022-07-21 11:31:05 +02:00
Maksim Eltyshev ddf9a32ea9 chore: Update version 2022-06-24 17:20:44 +02:00
Maksim Eltyshev e01a2eafba fix: Fix crash when uploading attachment 2022-06-24 17:17:42 +02:00
Maksim Eltyshev 3b3bff1b6b fix: Allow to use endpoints via http 2022-06-24 11:59:04 +02:00
Maksim Eltyshev ecc45c7a9e chore: Update version 2022-06-22 18:19:34 +02:00
Maksim Eltyshev 3497e09703 feat: Add pdf viewer to attachments gallery 2022-06-22 17:01:35 +02:00
Maksim Eltyshev dfc52191fc chore: Update version 2022-06-22 09:09:44 +02:00
Maksim Eltyshev 6268ced4cb ref: Collapse image data into one column 2022-06-21 12:01:41 +02:00
Maksim Eltyshev e7495e0067 fix: Fix routes again 2022-06-20 18:48:13 +02:00
Maksim Eltyshev b8c065e0c8 fix: Fix routes 2022-06-20 18:46:03 +02:00
Maksim Eltyshev 86e4864d1b feat: Add gallery for attachments 2022-06-20 18:27:39 +02:00
Maksim Eltyshev 0be598ca9e chore: Update version 2022-06-17 18:17:08 +02:00
Dominik Dancs ba850801c5 feat: Add Slovak translation (#265) 2022-06-17 20:16:15 +05:00
Maksim Eltyshev c9b23652d1 feat: Add ability to edit users
Closes #60
2022-06-15 14:13:22 +02:00
Maksim Eltyshev 8490629e4c fix: Fix password edit popup styles 2022-06-13 17:33:26 +02:00
ejo090 76fcd79203 fix: Fix markdown line breaks
Closes #257
2022-06-06 17:37:43 +05:00
Maksim Eltyshev 3ce1bc0419 fix: Fix dependencies installation 2022-05-21 23:52:12 +05:00
Maksim Eltyshev 540fbfceb5 chore: Update version 2022-05-21 21:49:36 +05:00
Maksim Eltyshev 48a56648e2 chore: Update version 2022-05-21 21:30:38 +05:00
Maksim Eltyshev 6d002c311c fix: Fix Swedish language code 2022-05-21 21:29:33 +05:00
Maksim Eltyshev ede707ed24 fix: Fix dependencies installation for multi-arch images 2022-05-21 21:01:43 +05:00
André Wahlberg 81052e1ce6 feat: Add Swedish translation (#246) 2022-05-21 20:39:17 +05:00
Maksim Eltyshev 315a9a4c3d ci: Fix build-args input name 2022-05-05 02:46:30 +05:00
Maksim Eltyshev 8112bcbb84 ci: Fix build_args 2022-05-05 00:34:26 +05:00
Maksim Eltyshev fc69b7d6f4 ci: Add manual trigger to build-and-push-docker-base-image action 2022-05-05 00:18:00 +05:00
Aliaksandr Shulyak 027e911907 Improvements for docker build process (#237) 2022-05-05 00:04:19 +05:00
Maksim Eltyshev afc2a4628d chore: Update version 2022-04-30 20:34:56 +05:00
Maksim Eltyshev b0b6587eee fix: Fix attachment headers
Closes #231
2022-04-29 17:21:39 +05:00
Aliaksandr Shulyak e29c9e6dda ci: GitHub actions config for docker builds (#221) 2022-04-29 17:15:59 +05:00
420 changed files with 20943 additions and 13618 deletions
+1
View File
@@ -7,4 +7,5 @@
server/public/* server/public/*
server/views/* server/views/*
server/logs
client/build client/build
+13
View File
@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: meltyshev
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -0,0 +1,44 @@
name: Build and push Docker base image
on:
push:
paths:
- ./Dockerfile.base
branches:
- master
workflow_dispatch:
env:
ALPINE_VERSION: 3.15
jobs:
build-and-push-docker-base-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: Dockerfile.base
build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }}
push: true
tags: |
ghcr.io/plankanban/planka:base-latest
ghcr.io/plankanban/planka:base-${{ env.ALPINE_VERSION }}
@@ -0,0 +1,42 @@
name: Build and push Docker image
on:
release:
types: [created]
jobs:
build-and-push-docker-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set version
uses: actions/github-script@v6
id: set-version
with:
result-encoding: string
script: return context.payload.release.tag_name.replace('v', '')
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
ghcr.io/plankanban/planka:latest
ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }}
+1 -1
View File
@@ -1,5 +1,5 @@
node_modules node_modules
docker-compose.override.yml docker-compose.override.yml
.vscode .idea
.DS_Store .DS_Store
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}
+18
View File
@@ -0,0 +1,18 @@
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"eslint.format.enable": true,
"eslint.workingDirectories": [
"./client",
"./server"
],
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
+3 -30
View File
@@ -10,47 +10,20 @@ RUN npm install npm@latest --global \
COPY client . COPY client .
RUN npm run build RUN npm run build
FROM node:lts-alpine FROM ghcr.io/plankanban/planka:base-latest
WORKDIR /app WORKDIR /app
COPY server/.npmrc server/package.json server/package-lock.json ./ COPY server/.npmrc server/package.json server/package-lock.json ./
ARG ALPINE_VERSION=3.15 RUN npm install npm@latest --global \
ARG VIPS_VERSION=8.12.2
RUN apk -U upgrade \
&& apk add \
bash giflib glib lcms2 libexif \
libgsf libjpeg-turbo libpng librsvg libwebp \
orc pango tiff \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
--no-cache \
&& apk add \
build-base giflib-dev glib-dev lcms2-dev libexif-dev \
libgsf-dev libjpeg-turbo-dev libpng-dev librsvg-dev libwebp-dev \
orc-dev pango-dev tiff-dev \
--virtual vips-dependencies \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
--no-cache \
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.gz | tar xzC /tmp \
&& cd /tmp/vips-${VIPS_VERSION} \
&& ./configure \
&& make \
&& make install-strip \
&& cd $OLDPWD \
&& rm -rf /tmp/vips-${VIPS_VERSION} \
&& npm install npm@latest --global \
&& npm install --production \ && npm install --production \
&& apk del vips-dependencies --purge && apk del vips-dependencies --purge
COPY docker-start.sh start.sh COPY docker-start.sh start.sh
COPY server . COPY server .
RUN chmod +x start.sh \ RUN cp .env.sample .env
&& cp .env.sample .env
COPY --from=client-builder /app/build public COPY --from=client-builder /app/build public
COPY --from=client-builder /app/build/index.html views COPY --from=client-builder /app/build/index.html views
+27
View File
@@ -0,0 +1,27 @@
FROM node:lts-alpine
ARG ALPINE_VERSION=3.15
ARG VIPS_VERSION=8.12.2
RUN apk -U upgrade \
&& apk add \
bash giflib glib lcms2 libexif \
libgsf libjpeg-turbo libpng librsvg libwebp \
orc pango tiff \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
--no-cache \
&& apk add \
build-base giflib-dev glib-dev lcms2-dev libexif-dev \
libgsf-dev libjpeg-turbo-dev libpng-dev librsvg-dev libwebp-dev \
orc-dev pango-dev tiff-dev \
--virtual vips-dependencies \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
--no-cache \
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.gz | tar xzC /tmp \
&& cd /tmp/vips-${VIPS_VERSION} \
&& ./configure \
&& make \
&& make install-strip \
&& rm -rf /tmp/vips-${VIPS_VERSION}
+73
View File
@@ -44,6 +44,79 @@ docker-compose up -d
Demo user: demo@demo.demo demo Demo user: demo@demo.demo demo
### Logging
Planka currently allows you to expose the applciation's logfile directory to the host machine via a shared volume. This feature is not enabled by default.
To expose the logfile director to the host machine, add the item `./logs/:/app/logs/` under `services.planka.volumes`.
Note that the directory to the left of the semicolon is regarding the host machine while the directory to the right of the semicolon is regarding the Docker container.
For example, in the above step, `./logs/:/app/logs/` will create the folder `logs` in the same directory where the `docker-compose.yml` file lives.
### Rotating Logs
Logrotate is designed to ease administration of systems that generate large numbers of log files. It allows automatic rotation, compression, removal, and mailing of log files. Each log file may be handled daily, weekly, monthly, or when it grows too large.
#### Setup logrotate for Planka logs
Create a file in `/etc/logrotate.d` named `planka` with the following contents:
```
/path/to/planka/logs/planka.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
}
```
Ensure to replace logfile directory with your installation’s `/logs/planka.log` location.
Restart the logrotate service.
### Fail2ban
Fail2ban is a service that uses iptables to automatically drop connections for a pre-defined amount of time from IPs that continuously failed to authenticate to the configured services.
#### Setup a filter and a jail for Planka
A filter defines regex rules to identify when users fail to authenticate on Planka's user interface.
Create a file in `/etc/fail2ban/filter.d` named `planka.conf` with the following contents:
```conf
[Definition]
failregex = ^(.*) Invalid (email or username:|password!) (\"(.*)\"!)? ?\(IP: <ADDR>\)$
ignoreregex =
```
The jail file defines how to handle the failed authentication attempts found by the Planka filter.
Create a file in `/etc/fail2ban/jail.d` named `planka.local` with the following contents:
```conf
[planka]
enabled = true
port = http,https
filter = planka
logpath = /path/to/planka/logs/planka.log
maxretry = 5
bantime = 900
```
Ensure to replace `logpath`'s value with your installation’s `/logs/planka.log` location. If you are using ports other than 80 and 443 for your Web server you should replace those too. The bantime and findtime are defined in seconds.
Restart the fail2ban service. You can check the status of your Planka jail by running:
```bash
fail2ban-client status planka
```
## Development ## Development
Clone the repository and install dependencies: Clone the repository and install dependencies:
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
Most recent release.
## Reporting a Vulnerability
Please report any security issues you discovered to meltyshev@gmail.com. If the issue is confirmed, we will release a patch as soon as possible depending on complexity.
**Do NOT create public issues on GitHub for security vulnerabilities.**
Thank you for your contribution!
+9807 -6152
View File
File diff suppressed because it is too large Load Diff
+27 -22
View File
@@ -63,57 +63,62 @@
} }
}, },
"dependencies": { "dependencies": {
"classnames": "^2.2.6", "classnames": "^2.3.1",
"connected-react-router": "^6.9.2", "connected-react-router": "^6.9.3",
"date-fns": "^2.28.0", "date-fns": "^2.29.1",
"dequal": "^2.0.2", "dequal": "^2.0.3",
"history": "^4.10.1", "history": "^4.10.1",
"i18next": "^21.6.16", "i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^6.1.4",
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.17.20", "jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"node-sass": "^7.0.1", "node-sass": "^7.0.1",
"photoswipe": "^5.3.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.0.0", "react-beautiful-dnd": "^13.1.0",
"react-datepicker": "^4.7.0", "react-datepicker": "^4.8.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dropzone": "^12.0.5", "react-dropzone": "^14.2.2",
"react-i18next": "^11.16.6", "react-i18next": "^11.18.1",
"react-input-mask": "^2.0.4", "react-input-mask": "^2.0.4",
"react-markdown": "^8.0.2", "react-markdown": "^8.0.3",
"react-photoswipe-gallery": "^2.2.1",
"react-redux": "^7.2.8", "react-redux": "^7.2.8",
"react-router-dom": "^5.3.1", "react-router-dom": "^5.3.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-textarea-autosize": "^8.3.3", "react-textarea-autosize": "^8.3.4",
"redux": "^4.1.2", "redux": "^4.2.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-orm": "^0.16.2", "redux-orm": "^0.16.2",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"reselect": "^4.1.5", "reselect": "^4.1.6",
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.2", "semantic-ui-react": "^2.1.3",
"socket.io-client": "^2.3.1", "socket.io-client": "^2.3.1",
"validator": "^13.7.0", "validator": "^13.7.0",
"whatwg-fetch": "^3.5.0" "whatwg-fetch": "^3.6.2",
"zxcvbn": "^4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.7", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
"babel-preset-airbnb": "^5.0.0", "babel-preset-airbnb": "^5.0.0",
"chai": "^4.3.6", "chai": "^4.3.6",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"eslint": "^8.13.0", "eslint": "^8.20.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-react-hooks": "^4.6.0",
"jest-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2",
"prettier": "2.6.2", "prettier": "2.7.1",
"react-test-renderer": "^17.0.2" "react-test-renderer": "^17.0.2"
} }
} }
-22
View File
@@ -1,22 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
export const handleActionCreate = (action) => ({
type: ActionTypes.ACTION_CREATE_HANDLE,
payload: {
action,
},
});
export const handleActionUpdate = (action) => ({
type: ActionTypes.ACTION_UPDATE_HANDLE,
payload: {
action,
},
});
export const handleActionDelete = (action) => ({
type: ActionTypes.ACTION_DELETE_HANDLE,
payload: {
action,
},
});
-26
View File
@@ -1,26 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const fetchActions = (cardId) => ({
type: ActionTypes.ACTIONS_FETCH,
payload: {
cardId,
},
});
fetchActions.success = (cardId, actions, users) => ({
type: ActionTypes.ACTIONS_FETCH__SUCCESS,
payload: {
cardId,
actions,
users,
},
});
fetchActions.failure = (cardId, error) => ({
type: ActionTypes.ACTIONS_FETCH__FAILURE,
payload: {
cardId,
error,
},
});
+79
View File
@@ -0,0 +1,79 @@
import ActionTypes from '../constants/ActionTypes';
const fetchActivities = (cardId) => ({
type: ActionTypes.ACTIVITIES_FETCH,
payload: {
cardId,
},
});
fetchActivities.success = (cardId, activities, users) => ({
type: ActionTypes.ACTIVITIES_FETCH__SUCCESS,
payload: {
cardId,
activities,
users,
},
});
fetchActivities.failure = (cardId, error) => ({
type: ActionTypes.ACTIVITIES_FETCH__FAILURE,
payload: {
cardId,
error,
},
});
const toggleActivitiesDetails = (cardId, isVisible) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE,
payload: {
cardId,
isVisible,
},
});
toggleActivitiesDetails.success = (cardId, activities, users) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__SUCCESS,
payload: {
cardId,
activities,
users,
},
});
toggleActivitiesDetails.failure = (cardId, error) => ({
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__FAILURE,
payload: {
cardId,
error,
},
});
const handleActivityCreate = (activity) => ({
type: ActionTypes.ACTIVITY_CREATE_HANDLE,
payload: {
activity,
},
});
const handleActivityUpdate = (activity) => ({
type: ActionTypes.ACTIVITY_UPDATE_HANDLE,
payload: {
activity,
},
});
const handleActivityDelete = (activity) => ({
type: ActionTypes.ACTIVITY_DELETE_HANDLE,
payload: {
activity,
},
});
export default {
fetchActivities,
toggleActivitiesDetails,
handleActivityCreate,
handleActivityUpdate,
handleActivityDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createAttachment = (attachment) => ({ const createAttachment = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE, type: ActionTypes.ATTACHMENT_CREATE,
payload: { payload: {
attachment, attachment,
@@ -23,14 +23,14 @@ createAttachment.failure = (localId, error) => ({
}, },
}); });
export const handleAttachmentCreate = (attachment) => ({ const handleAttachmentCreate = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE_HANDLE, type: ActionTypes.ATTACHMENT_CREATE_HANDLE,
payload: { payload: {
attachment, attachment,
}, },
}); });
export const updateAttachment = (id, data) => ({ const updateAttachment = (id, data) => ({
type: ActionTypes.ATTACHMENT_UPDATE, type: ActionTypes.ATTACHMENT_UPDATE,
payload: { payload: {
id, id,
@@ -53,14 +53,14 @@ updateAttachment.failure = (id, error) => ({
}, },
}); });
export const handleAttachmentUpdate = (attachment) => ({ const handleAttachmentUpdate = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE_HANDLE, type: ActionTypes.ATTACHMENT_UPDATE_HANDLE,
payload: { payload: {
attachment, attachment,
}, },
}); });
export const deleteAttachment = (id) => ({ const deleteAttachment = (id) => ({
type: ActionTypes.ATTACHMENT_DELETE, type: ActionTypes.ATTACHMENT_DELETE,
payload: { payload: {
id, id,
@@ -82,9 +82,18 @@ deleteAttachment.failure = (id, error) => ({
}, },
}); });
export const handleAttachmentDelete = (attachment) => ({ const handleAttachmentDelete = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE_HANDLE, type: ActionTypes.ATTACHMENT_DELETE_HANDLE,
payload: { payload: {
attachment, attachment,
}, },
}); });
export default {
createAttachment,
handleAttachmentCreate,
updateAttachment,
handleAttachmentUpdate,
deleteAttachment,
handleAttachmentDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createBoardMembership = (boardMembership) => ({ const createBoardMembership = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE, type: ActionTypes.BOARD_MEMBERSHIP_CREATE,
payload: { payload: {
boardMembership, boardMembership,
@@ -23,7 +23,7 @@ createBoardMembership.failure = (localId, error) => ({
}, },
}); });
export const handleBoardMembershipCreate = ( const handleBoardMembershipCreate = (
boardMembership, boardMembership,
project, project,
board, board,
@@ -38,6 +38,7 @@ export const handleBoardMembershipCreate = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
deletedNotifications,
) => ({ ) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE, type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: { payload: {
@@ -55,6 +56,7 @@ export const handleBoardMembershipCreate = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
deletedNotifications,
}, },
}); });
@@ -67,7 +69,37 @@ handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) =
}, },
}); });
export const deleteBoardMembership = (id) => ({ const updateBoardMembership = (id, data) => ({
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE,
payload: {
id,
data,
},
});
updateBoardMembership.success = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__SUCCESS,
payload: {
boardMembership,
},
});
updateBoardMembership.failure = (id, error) => ({
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const handleBoardMembershipUpdate = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_UPDATE_HANDLE,
payload: {
boardMembership,
},
});
const deleteBoardMembership = (id) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE, type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: { payload: {
id, id,
@@ -89,9 +121,18 @@ deleteBoardMembership.failure = (id, error) => ({
}, },
}); });
export const handleBoardMembershipDelete = (boardMembership) => ({ const handleBoardMembershipDelete = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE, type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: { payload: {
boardMembership, boardMembership,
}, },
}); });
export default {
createBoardMembership,
handleBoardMembershipCreate,
updateBoardMembership,
handleBoardMembershipUpdate,
deleteBoardMembership,
handleBoardMembershipDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createBoard = (board) => ({ const createBoard = (board) => ({
type: ActionTypes.BOARD_CREATE, type: ActionTypes.BOARD_CREATE,
payload: { payload: {
board, board,
@@ -24,14 +24,14 @@ createBoard.failure = (localId, error) => ({
}, },
}); });
export const handleBoardCreate = (board) => ({ const handleBoardCreate = (board) => ({
type: ActionTypes.BOARD_CREATE_HANDLE, type: ActionTypes.BOARD_CREATE_HANDLE,
payload: { payload: {
board, board,
}, },
}); });
export const fetchBoard = (id) => ({ const fetchBoard = (id) => ({
type: ActionTypes.BOARD_FETCH, type: ActionTypes.BOARD_FETCH,
payload: { payload: {
id, id,
@@ -75,7 +75,7 @@ fetchBoard.failure = (id, error) => ({
}, },
}); });
export const updateBoard = (id, data) => ({ const updateBoard = (id, data) => ({
type: ActionTypes.BOARD_UPDATE, type: ActionTypes.BOARD_UPDATE,
payload: { payload: {
id, id,
@@ -98,14 +98,14 @@ updateBoard.failure = (id, error) => ({
}, },
}); });
export const handleBoardUpdate = (board) => ({ const handleBoardUpdate = (board) => ({
type: ActionTypes.BOARD_UPDATE_HANDLE, type: ActionTypes.BOARD_UPDATE_HANDLE,
payload: { payload: {
board, board,
}, },
}); });
export const deleteBoard = (id) => ({ const deleteBoard = (id) => ({
type: ActionTypes.BOARD_DELETE, type: ActionTypes.BOARD_DELETE,
payload: { payload: {
id, id,
@@ -127,9 +127,19 @@ deleteBoard.failure = (id, error) => ({
}, },
}); });
export const handleBoardDelete = (board) => ({ const handleBoardDelete = (board) => ({
type: ActionTypes.BOARD_DELETE_HANDLE, type: ActionTypes.BOARD_DELETE_HANDLE,
payload: { payload: {
board, board,
}, },
}); });
export default {
createBoard,
handleBoardCreate,
fetchBoard,
updateBoard,
handleBoardUpdate,
deleteBoard,
handleBoardDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createCard = (card) => ({ const createCard = (card) => ({
type: ActionTypes.CARD_CREATE, type: ActionTypes.CARD_CREATE,
payload: { payload: {
card, card,
@@ -23,14 +23,14 @@ createCard.failure = (localId, error) => ({
}, },
}); });
export const handleCardCreate = (card) => ({ const handleCardCreate = (card) => ({
type: ActionTypes.CARD_CREATE_HANDLE, type: ActionTypes.CARD_CREATE_HANDLE,
payload: { payload: {
card, card,
}, },
}); });
export const updateCard = (id, data) => ({ const updateCard = (id, data) => ({
type: ActionTypes.CARD_UPDATE, type: ActionTypes.CARD_UPDATE,
payload: { payload: {
id, id,
@@ -53,14 +53,14 @@ updateCard.failure = (id, error) => ({
}, },
}); });
export const handleCardUpdate = (card) => ({ const handleCardUpdate = (card) => ({
type: ActionTypes.CARD_UPDATE_HANDLE, type: ActionTypes.CARD_UPDATE_HANDLE,
payload: { payload: {
card, card,
}, },
}); });
export const deleteCard = (id) => ({ const deleteCard = (id) => ({
type: ActionTypes.CARD_DELETE, type: ActionTypes.CARD_DELETE,
payload: { payload: {
id, id,
@@ -82,9 +82,18 @@ deleteCard.failure = (id, error) => ({
}, },
}); });
export const handleCardDelete = (card) => ({ const handleCardDelete = (card) => ({
type: ActionTypes.CARD_DELETE_HANDLE, type: ActionTypes.CARD_DELETE_HANDLE,
payload: { payload: {
card, card,
}, },
}); });
export default {
createCard,
handleCardCreate,
updateCard,
handleCardUpdate,
deleteCard,
handleCardDelete,
};
-69
View File
@@ -1,69 +0,0 @@
import ActionTypes from '../constants/ActionTypes';
export const createCommentAction = (action) => ({
type: ActionTypes.COMMENT_ACTION_CREATE,
payload: {
action,
},
});
createCommentAction.success = (localId, action) => ({
type: ActionTypes.COMMENT_ACTION_CREATE__SUCCESS,
payload: {
localId,
action,
},
});
createCommentAction.failure = (localId, error) => ({
type: ActionTypes.COMMENT_ACTION_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const updateCommentAction = (id, data) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE,
payload: {
id,
data,
},
});
updateCommentAction.success = (action) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE__SUCCESS,
payload: {
action,
},
});
updateCommentAction.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTION_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteCommentAction = (id) => ({
type: ActionTypes.COMMENT_ACTION_DELETE,
payload: {
id,
},
});
deleteCommentAction.success = (action) => ({
type: ActionTypes.COMMENT_ACTION_DELETE__SUCCESS,
payload: {
action,
},
});
deleteCommentAction.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTION_DELETE__FAILURE,
payload: {
id,
error,
},
});
+75
View File
@@ -0,0 +1,75 @@
import ActionTypes from '../constants/ActionTypes';
const createCommentActivity = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE,
payload: {
activity,
},
});
createCommentActivity.success = (localId, activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE__SUCCESS,
payload: {
localId,
activity,
},
});
createCommentActivity.failure = (localId, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_CREATE__FAILURE,
payload: {
localId,
error,
},
});
const updateCommentActivity = (id, data) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE,
payload: {
id,
data,
},
});
updateCommentActivity.success = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__SUCCESS,
payload: {
activity,
},
});
updateCommentActivity.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__FAILURE,
payload: {
id,
error,
},
});
const deleteCommentActivity = (id) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE,
payload: {
id,
},
});
deleteCommentActivity.success = (activity) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE__SUCCESS,
payload: {
activity,
},
});
deleteCommentActivity.failure = (id, error) => ({
type: ActionTypes.COMMENT_ACTIVITY_DELETE__FAILURE,
payload: {
id,
error,
},
});
export default {
createCommentActivity,
updateCommentActivity,
deleteCommentActivity,
};
+13 -4
View File
@@ -1,7 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export const initializeCore = (
export const initializeCore = (
user, user,
board, board,
users, users,
@@ -16,7 +15,7 @@ export const initializeCore = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
actions, activities,
notifications, notifications,
) => ({ ) => ({
type: ActionTypes.CORE_INITIALIZE, type: ActionTypes.CORE_INITIALIZE,
@@ -35,7 +34,17 @@ export const initializeCore = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
actions, activities,
notifications, notifications,
}, },
}); });
const logout = () => ({
type: ActionTypes.LOGOUT,
payload: {},
});
export default {
initializeCore,
logout,
};
-22
View File
@@ -1,22 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const handleActionCreate = (action) => ({
type: EntryActionTypes.ACTION_CREATE_HANDLE,
payload: {
action,
},
});
export const handleActionUpdate = (action) => ({
type: EntryActionTypes.ACTION_UPDATE_HANDLE,
payload: {
action,
},
});
export const handleActionDelete = (action) => ({
type: EntryActionTypes.ACTION_DELETE_HANDLE,
payload: {
action,
},
});
-7
View File
@@ -1,7 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const fetchActionsInCurrentCard = () => ({
type: EntryActionTypes.ACTIONS_IN_CURRENT_CARD_FETCH,
payload: {},
});
@@ -1,29 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createMembershipInCurrentBoard = (data) => ({
type: EntryActionTypes.MEMBERSHIP_IN_CURRENT_BOARD_CREATE,
payload: {
data,
},
});
export const handleBoardMembershipCreate = (boardMembership) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: {
boardMembership,
},
});
export const deleteBoardMembership = (id) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: {
id,
},
});
export const handleBoardMembershipDelete = (boardMembership) => ({
type: EntryActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: {
boardMembership,
},
});
@@ -1,23 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createCommentActionInCurrentCard = (data) => ({
type: EntryActionTypes.COMMENT_ACTION_IN_CURRENT_CARD_CREATE,
payload: {
data,
},
});
export const updateCommentAction = (id, data) => ({
type: EntryActionTypes.COMMENT_ACTION_UPDATE,
payload: {
id,
data,
},
});
export const deleteCommentAction = (id) => ({
type: EntryActionTypes.COMMENT_ACTION_DELETE,
payload: {
id,
},
});
-7
View File
@@ -1,7 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const initializeCore = () => ({
type: EntryActionTypes.CORE_INITIALIZE,
payload: {},
});
-18
View File
@@ -1,18 +0,0 @@
export * from './socket';
export * from './login';
export * from './core';
export * from './modal';
export * from './user';
export * from './project';
export * from './project-manager';
export * from './board';
export * from './board-membership';
export * from './label';
export * from './list';
export * from './card';
export * from './task';
export * from './attachment';
export * from './actions';
export * from './action';
export * from './comment-action';
export * from './notification';
-18
View File
@@ -1,18 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const authenticate = (data) => ({
type: EntryActionTypes.AUTHENTICATE,
payload: {
data,
},
});
export const clearAuthenticateError = () => ({
type: EntryActionTypes.AUTHENTICATE_ERROR_CLEAR,
payload: {},
});
export const logout = () => ({
type: EntryActionTypes.LOGOUT,
payload: {},
});
-22
View File
@@ -1,22 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const handleNotificationCreate = (notification) => ({
type: EntryActionTypes.NOTIFICATION_CREATE_HANDLE,
payload: {
notification,
},
});
export const deleteNotification = (id) => ({
type: EntryActionTypes.NOTIFICATION_DELETE,
payload: {
id,
},
});
export const handleNotificationDelete = (notification) => ({
type: EntryActionTypes.NOTIFICATION_DELETE_HANDLE,
payload: {
notification,
},
});
@@ -1,29 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createManagerInCurrentProject = (data) => ({
type: EntryActionTypes.MANAGER_IN_CURRENT_PROJECT_CREATE,
payload: {
data,
},
});
export const handleProjectManagerCreate = (projectManager) => ({
type: EntryActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: {
projectManager,
},
});
export const deleteProjectManager = (id) => ({
type: EntryActionTypes.PROJECT_MANAGER_DELETE,
payload: {
id,
},
});
export const handleProjectManagerDelete = (projectManager) => ({
type: EntryActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: {
projectManager,
},
});
-11
View File
@@ -1,11 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const handleSocketDisconnect = () => ({
type: EntryActionTypes.SOCKET_DISCONNECT_HANDLE,
payload: {},
});
export const handleSocketReconnect = () => ({
type: EntryActionTypes.SOCKET_RECONNECT_HANDLE,
payload: {},
});
-44
View File
@@ -1,44 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createTaskInCurrentCard = (data) => ({
type: EntryActionTypes.TASK_IN_CURRENT_CARD_CREATE,
payload: {
data,
},
});
export const handleTaskCreate = (task) => ({
type: EntryActionTypes.TASK_CREATE_HANDLE,
payload: {
task,
},
});
export const updateTask = (id, data) => ({
type: EntryActionTypes.TASK_UPDATE,
payload: {
id,
data,
},
});
export const handleTaskUpdate = (task) => ({
type: EntryActionTypes.TASK_UPDATE_HANDLE,
payload: {
task,
},
});
export const deleteTask = (id) => ({
type: EntryActionTypes.TASK_DELETE,
payload: {
id,
},
});
export const handleTaskDelete = (task) => ({
type: EntryActionTypes.TASK_DELETE_HANDLE,
payload: {
task,
},
});
-157
View File
@@ -1,157 +0,0 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createUser = (data) => ({
type: EntryActionTypes.USER_CREATE,
payload: {
data,
},
});
export const handleUserCreate = (user) => ({
type: EntryActionTypes.USER_CREATE_HANDLE,
payload: {
user,
},
});
export const clearUserCreateError = () => ({
type: EntryActionTypes.USER_CREATE_ERROR_CLEAR,
payload: {},
});
export const updateUser = (id, data) => ({
type: EntryActionTypes.USER_UPDATE,
payload: {
id,
data,
},
});
export const updateCurrentUser = (data) => ({
type: EntryActionTypes.CURRENT_USER_UPDATE,
payload: {
data,
},
});
export const handleUserUpdate = (user) => ({
type: EntryActionTypes.USER_UPDATE_HANDLE,
payload: {
user,
},
});
export const updateCurrentUserEmail = (data) => ({
type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE,
payload: {
data,
},
});
export const clearCurrentUserEmailUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: {},
});
export const updateCurrentUserPassword = (data) => ({
type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE,
payload: {
data,
},
});
export const clearCurrentUserPasswordUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: {},
});
export const updateCurrentUserUsername = (data) => ({
type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE,
payload: {
data,
},
});
export const clearCurrentUserUsernameUpdateError = () => ({
type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: {},
});
export const updateCurrentUserAvatar = (data) => ({
type: EntryActionTypes.CURRENT_USER_AVATAR_UPDATE,
payload: {
data,
},
});
export const deleteUser = (id) => ({
type: EntryActionTypes.USER_DELETE,
payload: {
id,
},
});
export const handleUserDelete = (user) => ({
type: EntryActionTypes.USER_DELETE_HANDLE,
payload: {
user,
},
});
export const addUserToCard = (id, cardId) => ({
type: EntryActionTypes.USER_TO_CARD_ADD,
payload: {
id,
cardId,
},
});
export const addUserToCurrentCard = (id) => ({
type: EntryActionTypes.USER_TO_CURRENT_CARD_ADD,
payload: {
id,
},
});
export const handleUserToCardAdd = (cardMembership) => ({
type: EntryActionTypes.USER_TO_CARD_ADD_HANDLE,
payload: {
cardMembership,
},
});
export const removeUserFromCard = (id, cardId) => ({
type: EntryActionTypes.USER_FROM_CARD_REMOVE,
payload: {
id,
cardId,
},
});
export const removeUserFromCurrentCard = (id) => ({
type: EntryActionTypes.USER_FROM_CURRENT_CARD_REMOVE,
payload: {
id,
},
});
export const handleUserFromCardRemove = (cardMembership) => ({
type: EntryActionTypes.USER_FROM_CARD_REMOVE_HANDLE,
payload: {
cardMembership,
},
});
export const addUserToFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.USER_TO_FILTER_IN_CURRENT_BOARD_ADD,
payload: {
id,
},
});
export const removeUserFromFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.USER_FROM_FILTER_IN_CURRENT_BOARD_REMOVE,
payload: {
id,
},
});
+39 -19
View File
@@ -1,19 +1,39 @@
export * from './router'; import router from './router';
export * from './socket'; import socket from './socket';
export * from './login'; import login from './login';
export * from './core'; import core from './core';
export * from './modal'; import modals from './modals';
export * from './user'; import users from './users';
export * from './project'; import projects from './projects';
export * from './project-manager'; import projectManagers from './project-managers';
export * from './board'; import boards from './boards';
export * from './board-membership'; import boardMemberships from './board-memberships';
export * from './label'; import labels from './labels';
export * from './list'; import lists from './lists';
export * from './card'; import cards from './cards';
export * from './task'; import tasks from './tasks';
export * from './attachment'; import attachments from './attachments';
export * from './actions'; import activities from './activities';
export * from './action'; import commentActivities from './comment-activities';
export * from './comment-action'; import notifications from './notifications';
export * from './notification';
export default {
...router,
...socket,
...login,
...core,
...modals,
...users,
...projects,
...projectManagers,
...boards,
...boardMemberships,
...labels,
...lists,
...cards,
...tasks,
...attachments,
...activities,
...commentActivities,
...notifications,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createLabel = (label) => ({ const createLabel = (label) => ({
type: ActionTypes.LABEL_CREATE, type: ActionTypes.LABEL_CREATE,
payload: { payload: {
label, label,
@@ -23,14 +23,14 @@ createLabel.failure = (localId, error) => ({
}, },
}); });
export const handleLabelCreate = (label) => ({ const handleLabelCreate = (label) => ({
type: ActionTypes.LABEL_CREATE_HANDLE, type: ActionTypes.LABEL_CREATE_HANDLE,
payload: { payload: {
label, label,
}, },
}); });
export const updateLabel = (id, data) => ({ const updateLabel = (id, data) => ({
type: ActionTypes.LABEL_UPDATE, type: ActionTypes.LABEL_UPDATE,
payload: { payload: {
id, id,
@@ -53,14 +53,14 @@ updateLabel.failure = (id, error) => ({
}, },
}); });
export const handleLabelUpdate = (label) => ({ const handleLabelUpdate = (label) => ({
type: ActionTypes.LABEL_UPDATE_HANDLE, type: ActionTypes.LABEL_UPDATE_HANDLE,
payload: { payload: {
label, label,
}, },
}); });
export const deleteLabel = (id) => ({ const deleteLabel = (id) => ({
type: ActionTypes.LABEL_DELETE, type: ActionTypes.LABEL_DELETE,
payload: { payload: {
id, id,
@@ -82,14 +82,14 @@ deleteLabel.failure = (id, error) => ({
}, },
}); });
export const handleLabelDelete = (label) => ({ const handleLabelDelete = (label) => ({
type: ActionTypes.LABEL_DELETE_HANDLE, type: ActionTypes.LABEL_DELETE_HANDLE,
payload: { payload: {
label, label,
}, },
}); });
export const addLabelToCard = (id, cardId) => ({ const addLabelToCard = (id, cardId) => ({
type: ActionTypes.LABEL_TO_CARD_ADD, type: ActionTypes.LABEL_TO_CARD_ADD,
payload: { payload: {
id, id,
@@ -113,14 +113,14 @@ addLabelToCard.failure = (id, cardId, error) => ({
}, },
}); });
export const handleLabelToCardAdd = (cardLabel) => ({ const handleLabelToCardAdd = (cardLabel) => ({
type: ActionTypes.LABEL_TO_CARD_ADD_HANDLE, type: ActionTypes.LABEL_TO_CARD_ADD_HANDLE,
payload: { payload: {
cardLabel, cardLabel,
}, },
}); });
export const removeLabelFromCard = (id, cardId) => ({ const removeLabelFromCard = (id, cardId) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE, type: ActionTypes.LABEL_FROM_CARD_REMOVE,
payload: { payload: {
id, id,
@@ -144,14 +144,14 @@ removeLabelFromCard.failure = (id, cardId, error) => ({
}, },
}); });
export const handleLabelFromCardRemove = (cardLabel) => ({ const handleLabelFromCardRemove = (cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE, type: ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,
payload: { payload: {
cardLabel, cardLabel,
}, },
}); });
export const addLabelToBoardFilter = (id, boardId) => ({ const addLabelToBoardFilter = (id, boardId) => ({
type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD, type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,
payload: { payload: {
id, id,
@@ -159,10 +159,25 @@ export const addLabelToBoardFilter = (id, boardId) => ({
}, },
}); });
export const removeLabelFromBoardFilter = (id, boardId) => ({ const removeLabelFromBoardFilter = (id, boardId) => ({
type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE, type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE,
payload: { payload: {
id, id,
boardId, boardId,
}, },
}); });
export default {
createLabel,
handleLabelCreate,
updateLabel,
handleLabelUpdate,
deleteLabel,
handleLabelDelete,
addLabelToCard,
handleLabelToCardAdd,
removeLabelFromCard,
handleLabelFromCardRemove,
addLabelToBoardFilter,
removeLabelFromBoardFilter,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createList = (list) => ({ const createList = (list) => ({
type: ActionTypes.LIST_CREATE, type: ActionTypes.LIST_CREATE,
payload: { payload: {
list, list,
@@ -23,14 +23,14 @@ createList.failure = (localId, error) => ({
}, },
}); });
export const handleListCreate = (list) => ({ const handleListCreate = (list) => ({
type: ActionTypes.LIST_CREATE_HANDLE, type: ActionTypes.LIST_CREATE_HANDLE,
payload: { payload: {
list, list,
}, },
}); });
export const updateList = (id, data) => ({ const updateList = (id, data) => ({
type: ActionTypes.LIST_UPDATE, type: ActionTypes.LIST_UPDATE,
payload: { payload: {
id, id,
@@ -53,14 +53,14 @@ updateList.failure = (id, error) => ({
}, },
}); });
export const handleListUpdate = (list) => ({ const handleListUpdate = (list) => ({
type: ActionTypes.LIST_UPDATE_HANDLE, type: ActionTypes.LIST_UPDATE_HANDLE,
payload: { payload: {
list, list,
}, },
}); });
export const deleteList = (id) => ({ const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE, type: ActionTypes.LIST_DELETE,
payload: { payload: {
id, id,
@@ -82,9 +82,18 @@ deleteList.failure = (id, error) => ({
}, },
}); });
export const handleListDelete = (list) => ({ const handleListDelete = (list) => ({
type: ActionTypes.LIST_DELETE_HANDLE, type: ActionTypes.LIST_DELETE_HANDLE,
payload: { payload: {
list, list,
}, },
}); });
export default {
createList,
handleListCreate,
updateList,
handleListUpdate,
deleteList,
handleListDelete,
};
+6 -6
View File
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const authenticate = (data) => ({ const authenticate = (data) => ({
type: ActionTypes.AUTHENTICATE, type: ActionTypes.AUTHENTICATE,
payload: { payload: {
data, data,
@@ -21,12 +21,12 @@ authenticate.failure = (error) => ({
}, },
}); });
export const clearAuthenticateError = () => ({ const clearAuthenticateError = () => ({
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR, type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
payload: {}, payload: {},
}); });
export const logout = () => ({ export default {
type: ActionTypes.LOGOUT, authenticate,
payload: {}, clearAuthenticateError,
}); };
@@ -1,13 +1,18 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const openModal = (type) => ({ const openModal = (type) => ({
type: ActionTypes.MODAL_OPEN, type: ActionTypes.MODAL_OPEN,
payload: { payload: {
type, type,
}, },
}); });
export const closeModal = () => ({ const closeModal = () => ({
type: ActionTypes.MODAL_CLOSE, type: ActionTypes.MODAL_CLOSE,
payload: {}, payload: {},
}); });
export default {
openModal,
closeModal,
};
@@ -1,16 +1,16 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const handleNotificationCreate = (notification, users, cards, actions) => ({ const handleNotificationCreate = (notification, users, cards, activities) => ({
type: ActionTypes.NOTIFICATION_CREATE_HANDLE, type: ActionTypes.NOTIFICATION_CREATE_HANDLE,
payload: { payload: {
notification, notification,
users, users,
cards, cards,
actions, activities,
}, },
}); });
export const deleteNotification = (id) => ({ const deleteNotification = (id) => ({
type: ActionTypes.NOTIFICATION_DELETE, type: ActionTypes.NOTIFICATION_DELETE,
payload: { payload: {
id, id,
@@ -32,9 +32,15 @@ deleteNotification.failure = (id, error) => ({
}, },
}); });
export const handleNotificationDelete = (notification) => ({ const handleNotificationDelete = (notification) => ({
type: ActionTypes.NOTIFICATION_DELETE_HANDLE, type: ActionTypes.NOTIFICATION_DELETE_HANDLE,
payload: { payload: {
notification, notification,
}, },
}); });
export default {
handleNotificationCreate,
deleteNotification,
handleNotificationDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createProjectManager = (projectManager) => ({ const createProjectManager = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE, type: ActionTypes.PROJECT_MANAGER_CREATE,
payload: { payload: {
projectManager, projectManager,
@@ -23,7 +23,7 @@ createProjectManager.failure = (localId, error) => ({
}, },
}); });
export const handleProjectManagerCreate = ( const handleProjectManagerCreate = (
projectManager, projectManager,
project, project,
board, board,
@@ -38,6 +38,7 @@ export const handleProjectManagerCreate = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
deletedNotifications,
) => ({ ) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE, type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: { payload: {
@@ -55,6 +56,7 @@ export const handleProjectManagerCreate = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
deletedNotifications,
}, },
}); });
@@ -67,7 +69,7 @@ handleProjectManagerCreate.fetchProject = (id, currentUserId, currentBoardId) =>
}, },
}); });
export const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({ const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE, type: ActionTypes.PROJECT_MANAGER_DELETE,
payload: { payload: {
id, id,
@@ -91,7 +93,7 @@ deleteProjectManager.failure = (id, error) => ({
}, },
}); });
export const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({ const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE, type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: { payload: {
projectManager, projectManager,
@@ -99,3 +101,10 @@ export const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurr
isCurrentProject, isCurrentProject,
}, },
}); });
export default {
createProjectManager,
handleProjectManagerCreate,
deleteProjectManager,
handleProjectManagerDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createProject = (data) => ({ const createProject = (data) => ({
type: ActionTypes.PROJECT_CREATE, type: ActionTypes.PROJECT_CREATE,
payload: { payload: {
data, data,
@@ -22,7 +22,7 @@ createProject.failure = (error) => ({
}, },
}); });
export const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({ const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({
type: ActionTypes.PROJECT_CREATE_HANDLE, type: ActionTypes.PROJECT_CREATE_HANDLE,
payload: { payload: {
project, project,
@@ -33,7 +33,7 @@ export const handleProjectCreate = (project, users, projectManagers, boards, boa
}, },
}); });
export const updateProject = (id, data) => ({ const updateProject = (id, data) => ({
type: ActionTypes.PROJECT_UPDATE, type: ActionTypes.PROJECT_UPDATE,
payload: { payload: {
id, id,
@@ -56,14 +56,14 @@ updateProject.failure = (id, error) => ({
}, },
}); });
export const handleProjectUpdate = (project) => ({ const handleProjectUpdate = (project) => ({
type: ActionTypes.PROJECT_UPDATE_HANDLE, type: ActionTypes.PROJECT_UPDATE_HANDLE,
payload: { payload: {
project, project,
}, },
}); });
export const updateProjectBackgroundImage = (id) => ({ const updateProjectBackgroundImage = (id) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE, type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: { payload: {
id, id,
@@ -85,7 +85,7 @@ updateProjectBackgroundImage.failure = (id, error) => ({
}, },
}); });
export const deleteProject = (id) => ({ const deleteProject = (id) => ({
type: ActionTypes.PROJECT_DELETE, type: ActionTypes.PROJECT_DELETE,
payload: { payload: {
id, id,
@@ -107,9 +107,19 @@ deleteProject.failure = (id, error) => ({
}, },
}); });
export const handleProjectDelete = (project) => ({ const handleProjectDelete = (project) => ({
type: ActionTypes.PROJECT_DELETE_HANDLE, type: ActionTypes.PROJECT_DELETE_HANDLE,
payload: { payload: {
project, project,
}, },
}); });
export default {
createProject,
handleProjectCreate,
updateProject,
handleProjectUpdate,
updateProjectBackgroundImage,
deleteProject,
handleProjectDelete,
};
+7 -4
View File
@@ -1,7 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export const handleLocationChange = (
export const handleLocationChange = (
board, board,
users, users,
projects, projects,
@@ -13,7 +12,7 @@ export const handleLocationChange = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
notifications, deletedNotifications,
) => ({ ) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE, type: ActionTypes.LOCATION_CHANGE_HANDLE,
payload: { payload: {
@@ -28,7 +27,7 @@ export const handleLocationChange = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
notifications, deletedNotifications,
}, },
}); });
@@ -38,3 +37,7 @@ handleLocationChange.fetchBoard = (id) => ({
id, id,
}, },
}); });
export default {
handleLocationChange,
};
+9 -4
View File
@@ -1,11 +1,11 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const handleSocketDisconnect = () => ({ const handleSocketDisconnect = () => ({
type: ActionTypes.SOCKET_DISCONNECT_HANDLE, type: ActionTypes.SOCKET_DISCONNECT_HANDLE,
payload: {}, payload: {},
}); });
export const handleSocketReconnect = ( const handleSocketReconnect = (
user, user,
board, board,
users, users,
@@ -20,7 +20,7 @@ export const handleSocketReconnect = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
actions, activities,
notifications, notifications,
) => ({ ) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE, type: ActionTypes.SOCKET_RECONNECT_HANDLE,
@@ -39,7 +39,7 @@ export const handleSocketReconnect = (
cardLabels, cardLabels,
tasks, tasks,
attachments, attachments,
actions, activities,
notifications, notifications,
}, },
}); });
@@ -51,3 +51,8 @@ handleSocketReconnect.fetchCore = (currentUserId, currentBoardId) => ({
currentBoardId, currentBoardId,
}, },
}); });
export default {
handleSocketDisconnect,
handleSocketReconnect,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createTask = (task) => ({ const createTask = (task) => ({
type: ActionTypes.TASK_CREATE, type: ActionTypes.TASK_CREATE,
payload: { payload: {
task, task,
@@ -23,14 +23,14 @@ createTask.failure = (localId, error) => ({
}, },
}); });
export const handleTaskCreate = (task) => ({ const handleTaskCreate = (task) => ({
type: ActionTypes.TASK_CREATE_HANDLE, type: ActionTypes.TASK_CREATE_HANDLE,
payload: { payload: {
task, task,
}, },
}); });
export const updateTask = (id, data) => ({ const updateTask = (id, data) => ({
type: ActionTypes.TASK_UPDATE, type: ActionTypes.TASK_UPDATE,
payload: { payload: {
id, id,
@@ -53,14 +53,14 @@ updateTask.failure = (id, error) => ({
}, },
}); });
export const handleTaskUpdate = (task) => ({ const handleTaskUpdate = (task) => ({
type: ActionTypes.TASK_UPDATE_HANDLE, type: ActionTypes.TASK_UPDATE_HANDLE,
payload: { payload: {
task, task,
}, },
}); });
export const deleteTask = (id) => ({ const deleteTask = (id) => ({
type: ActionTypes.TASK_DELETE, type: ActionTypes.TASK_DELETE,
payload: { payload: {
id, id,
@@ -82,9 +82,18 @@ deleteTask.failure = (id, error) => ({
}, },
}); });
export const handleTaskDelete = (task) => ({ const handleTaskDelete = (task) => ({
type: ActionTypes.TASK_DELETE_HANDLE, type: ActionTypes.TASK_DELETE_HANDLE,
payload: { payload: {
task, task,
}, },
}); });
export default {
createTask,
handleTaskCreate,
updateTask,
handleTaskUpdate,
deleteTask,
handleTaskDelete,
};
@@ -1,6 +1,6 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export const createUser = (data) => ({ const createUser = (data) => ({
type: ActionTypes.USER_CREATE, type: ActionTypes.USER_CREATE,
payload: { payload: {
data, data,
@@ -21,19 +21,19 @@ createUser.failure = (error) => ({
}, },
}); });
export const handleUserCreate = (user) => ({ const handleUserCreate = (user) => ({
type: ActionTypes.USER_CREATE_HANDLE, type: ActionTypes.USER_CREATE_HANDLE,
payload: { payload: {
user, user,
}, },
}); });
export const clearUserCreateError = () => ({ const clearUserCreateError = () => ({
type: ActionTypes.USER_CREATE_ERROR_CLEAR, type: ActionTypes.USER_CREATE_ERROR_CLEAR,
payload: {}, payload: {},
}); });
export const updateUser = (id, data) => ({ const updateUser = (id, data) => ({
type: ActionTypes.USER_UPDATE, type: ActionTypes.USER_UPDATE,
payload: { payload: {
id, id,
@@ -56,7 +56,7 @@ updateUser.failure = (id, error) => ({
}, },
}); });
export const handleUserUpdate = (user, users, isCurrent) => ({ const handleUserUpdate = (user, users, isCurrent) => ({
type: ActionTypes.USER_UPDATE_HANDLE, type: ActionTypes.USER_UPDATE_HANDLE,
payload: { payload: {
user, user,
@@ -65,7 +65,7 @@ export const handleUserUpdate = (user, users, isCurrent) => ({
}, },
}); });
export const updateUserEmail = (id, data) => ({ const updateUserEmail = (id, data) => ({
type: ActionTypes.USER_EMAIL_UPDATE, type: ActionTypes.USER_EMAIL_UPDATE,
payload: { payload: {
id, id,
@@ -88,14 +88,14 @@ updateUserEmail.failure = (id, error) => ({
}, },
}); });
export const clearUserEmailUpdateError = (id) => ({ const clearUserEmailUpdateError = (id) => ({
type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: { payload: {
id, id,
}, },
}); });
export const updateUserPassword = (id, data) => ({ const updateUserPassword = (id, data) => ({
type: ActionTypes.USER_PASSWORD_UPDATE, type: ActionTypes.USER_PASSWORD_UPDATE,
payload: { payload: {
id, id,
@@ -118,14 +118,14 @@ updateUserPassword.failure = (id, error) => ({
}, },
}); });
export const clearUserPasswordUpdateError = (id) => ({ const clearUserPasswordUpdateError = (id) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: { payload: {
id, id,
}, },
}); });
export const updateUserUsername = (id, data) => ({ const updateUserUsername = (id, data) => ({
type: ActionTypes.USER_USERNAME_UPDATE, type: ActionTypes.USER_USERNAME_UPDATE,
payload: { payload: {
id, id,
@@ -148,14 +148,14 @@ updateUserUsername.failure = (id, error) => ({
}, },
}); });
export const clearUserUsernameUpdateError = (id) => ({ const clearUserUsernameUpdateError = (id) => ({
type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR, type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: { payload: {
id, id,
}, },
}); });
export const updateUserAvatar = (id) => ({ const updateUserAvatar = (id) => ({
type: ActionTypes.USER_AVATAR_UPDATE, type: ActionTypes.USER_AVATAR_UPDATE,
payload: { payload: {
id, id,
@@ -177,7 +177,7 @@ updateUserAvatar.failure = (id, error) => ({
}, },
}); });
export const deleteUser = (id) => ({ const deleteUser = (id) => ({
type: ActionTypes.USER_DELETE, type: ActionTypes.USER_DELETE,
payload: { payload: {
id, id,
@@ -199,14 +199,14 @@ deleteUser.failure = (id, error) => ({
}, },
}); });
export const handleUserDelete = (user) => ({ const handleUserDelete = (user) => ({
type: ActionTypes.USER_DELETE_HANDLE, type: ActionTypes.USER_DELETE_HANDLE,
payload: { payload: {
user, user,
}, },
}); });
export const addUserToCard = (id, cardId, isCurrent) => ({ const addUserToCard = (id, cardId, isCurrent) => ({
type: ActionTypes.USER_TO_CARD_ADD, type: ActionTypes.USER_TO_CARD_ADD,
payload: { payload: {
id, id,
@@ -231,14 +231,14 @@ addUserToCard.failure = (id, cardId, error) => ({
}, },
}); });
export const handleUserToCardAdd = (cardMembership) => ({ const handleUserToCardAdd = (cardMembership) => ({
type: ActionTypes.USER_TO_CARD_ADD_HANDLE, type: ActionTypes.USER_TO_CARD_ADD_HANDLE,
payload: { payload: {
cardMembership, cardMembership,
}, },
}); });
export const removeUserFromCard = (id, cardId) => ({ const removeUserFromCard = (id, cardId) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE, type: ActionTypes.USER_FROM_CARD_REMOVE,
payload: { payload: {
id, id,
@@ -262,14 +262,14 @@ removeUserFromCard.failure = (id, cardId, error) => ({
}, },
}); });
export const handleUserFromCardRemove = (cardMembership) => ({ const handleUserFromCardRemove = (cardMembership) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE_HANDLE, type: ActionTypes.USER_FROM_CARD_REMOVE_HANDLE,
payload: { payload: {
cardMembership, cardMembership,
}, },
}); });
export const addUserToBoardFilter = (id, boardId) => ({ const addUserToBoardFilter = (id, boardId) => ({
type: ActionTypes.USER_TO_BOARD_FILTER_ADD, type: ActionTypes.USER_TO_BOARD_FILTER_ADD,
payload: { payload: {
id, id,
@@ -277,10 +277,33 @@ export const addUserToBoardFilter = (id, boardId) => ({
}, },
}); });
export const removeUserFromBoardFilter = (id, boardId) => ({ const removeUserFromBoardFilter = (id, boardId) => ({
type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE, type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE,
payload: { payload: {
id, id,
boardId, boardId,
}, },
}); });
export default {
createUser,
handleUserCreate,
clearUserCreateError,
updateUser,
handleUserUpdate,
updateUserEmail,
clearUserEmailUpdateError,
updateUserPassword,
clearUserPasswordUpdateError,
updateUserUsername,
clearUserUsernameUpdateError,
updateUserAvatar,
deleteUser,
handleUserDelete,
addUserToCard,
handleUserToCardAdd,
removeUserFromCard,
handleUserFromCardRemove,
addUserToBoardFilter,
removeUserFromBoardFilter,
};
+1 -1
View File
@@ -2,7 +2,7 @@ import http from './http';
/* Actions */ /* Actions */
const createAccessToken = (data) => http.post('/access-tokens', data); const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
export default { export default {
createAccessToken, createAccessToken,
-36
View File
@@ -1,36 +0,0 @@
import socket from './socket';
/* Transformers */
export const transformAction = (action) => ({
...action,
createdAt: new Date(action.createdAt),
});
/* Actions */
const getActions = (cardId, data) =>
socket.get(`/cards/${cardId}/actions`, data).then((body) => ({
...body,
items: body.items.map(transformAction),
}));
/* Event handlers */
const makeHandleActionCreate = (next) => (body) => {
next({
...body,
item: transformAction(body.item),
});
};
const makeHandleActionUpdate = makeHandleActionCreate;
const makeHandleActionDelete = makeHandleActionCreate;
export default {
getActions,
makeHandleActionCreate,
makeHandleActionUpdate,
makeHandleActionDelete,
};
+36
View File
@@ -0,0 +1,36 @@
import socket from './socket';
/* Transformers */
export const transformActivity = (activity) => ({
...activity,
createdAt: new Date(activity.createdAt),
});
/* Actions */
const getActivities = (cardId, data, headers) =>
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
...body,
items: body.items.map(transformActivity),
}));
/* Event handlers */
const makeHandleActivityCreate = (next) => (body) => {
next({
...body,
item: transformActivity(body.item),
});
};
const makeHandleActivityUpdate = makeHandleActivityCreate;
const makeHandleActivityDelete = makeHandleActivityCreate;
export default {
getActivities,
makeHandleActivityCreate,
makeHandleActivityUpdate,
makeHandleActivityDelete,
};
+6 -6
View File
@@ -10,20 +10,20 @@ export const transformAttachment = (attachment) => ({
/* Actions */ /* Actions */
const createAttachment = (cardId, data, requestId) => const createAttachment = (cardId, data, requestId, headers) =>
http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data).then((body) => ({ http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data, headers).then((body) => ({
...body, ...body,
item: transformAttachment(body.item), item: transformAttachment(body.item),
})); }));
const updateAttachment = (id, data) => const updateAttachment = (id, data, headers) =>
socket.patch(`/attachments/${id}`, data).then((body) => ({ socket.patch(`/attachments/${id}`, data, headers).then((body) => ({
...body, ...body,
item: transformAttachment(body.item), item: transformAttachment(body.item),
})); }));
const deleteAttachment = (id) => const deleteAttachment = (id, headers) =>
socket.delete(`/attachments/${id}`).then((body) => ({ socket.delete(`/attachments/${id}`, undefined, headers).then((body) => ({
...body, ...body,
item: transformAttachment(body.item), item: transformAttachment(body.item),
})); }));
+8 -3
View File
@@ -2,12 +2,17 @@ import socket from './socket';
/* Actions */ /* Actions */
const createBoardMembership = (boardId, data) => const createBoardMembership = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/memberships`, data); socket.post(`/boards/${boardId}/memberships`, data, headers);
const deleteBoardMembership = (id) => socket.delete(`/board-memberships/${id}`); const updateBoardMembership = (id, data, headers) =>
socket.patch(`/board-memberships/${id}`, data, headers);
const deleteBoardMembership = (id, headers) =>
socket.delete(`/board-memberships/${id}`, undefined, headers);
export default { export default {
createBoardMembership, createBoardMembership,
updateBoardMembership,
deleteBoardMembership, deleteBoardMembership,
}; };
+6 -5
View File
@@ -4,10 +4,11 @@ import { transformAttachment } from './attachments';
/* Actions */ /* Actions */
const createBoard = (projectId, data) => socket.post(`/projects/${projectId}/boards`, data); const createBoard = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/boards`, data, headers);
const getBoard = (id) => const getBoard = (id, headers) =>
socket.get(`/boards/${id}`).then((body) => ({ socket.get(`/boards/${id}`, undefined, headers).then((body) => ({
...body, ...body,
included: { included: {
...body.included, ...body.included,
@@ -16,9 +17,9 @@ const getBoard = (id) =>
}, },
})); }));
const updateBoard = (id, data) => socket.patch(`/boards/${id}`, data); const updateBoard = (id, data, headers) => socket.patch(`/boards/${id}`, data, headers);
const deleteBoard = (id) => socket.delete(`/boards/${id}`); const deleteBoard = (id, headers) => socket.delete(`/boards/${id}`, undefined, headers);
export default { export default {
createBoard, createBoard,
+4 -2
View File
@@ -2,9 +2,11 @@ import socket from './socket';
/* Actions */ /* Actions */
const createCardLabel = (cardId, data) => socket.post(`/cards/${cardId}/labels`, data); const createCardLabel = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/labels`, data, headers);
const deleteCardLabel = (cardId, labelId) => socket.delete(`/cards/${cardId}/labels/${labelId}`); const deleteCardLabel = (cardId, labelId, headers) =>
socket.delete(`/cards/${cardId}/labels/${labelId}`, undefined, headers);
export default { export default {
createCardLabel, createCardLabel,
+4 -3
View File
@@ -2,10 +2,11 @@ import socket from './socket';
/* Actions */ /* Actions */
const createCardMembership = (cardId, data) => socket.post(`/cards/${cardId}/memberships`, data); const createCardMembership = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/memberships`, data, headers);
const deleteCardMembership = (cardId, userId) => const deleteCardMembership = (cardId, userId, headers) =>
socket.delete(`/cards/${cardId}/memberships?userId=${userId}`); socket.delete(`/cards/${cardId}/memberships?userId=${userId}`, undefined, headers);
export default { export default {
createCardMembership, createCardMembership,
+12 -17
View File
@@ -35,8 +35,8 @@ export const transformCardData = (data) => ({
/* Actions */ /* Actions */
const getCards = (boardId, data) => const getCards = (boardId, data, headers) =>
socket.get(`/board/${boardId}/cards`, data).then((body) => ({ socket.get(`/board/${boardId}/cards`, data, headers).then((body) => ({
...body, ...body,
items: body.items.map(transformCard), items: body.items.map(transformCard),
included: { included: {
@@ -45,26 +45,26 @@ const getCards = (boardId, data) =>
}, },
})); }));
const createCard = (boardId, data) => const createCard = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/cards`, transformCardData(data)).then((body) => ({ socket.post(`/boards/${boardId}/cards`, transformCardData(data), headers).then((body) => ({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
})); }));
const getCard = (id) => const getCard = (id, headers) =>
socket.get(`/cards/${id}`).then((body) => ({ socket.get(`/cards/${id}`, undefined, headers).then((body) => ({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
})); }));
const updateCard = (id, data) => const updateCard = (id, data, headers) =>
socket.patch(`/cards/${id}`, transformCardData(data)).then((body) => ({ socket.patch(`/cards/${id}`, transformCardData(data), headers).then((body) => ({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
})); }));
const deleteCard = (id) => const deleteCard = (id, headers) =>
socket.delete(`/cards/${id}`).then((body) => ({ socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({
...body, ...body,
item: transformCard(body.item), item: transformCard(body.item),
})); }));
@@ -78,14 +78,9 @@ const makeHandleCardCreate = (next) => (body) => {
}); });
}; };
const makeHandleCardUpdate = (next) => (body) => { const makeHandleCardUpdate = makeHandleCardCreate;
next({
...body,
item: transformCard(body.item),
});
};
const makeHandleCardDelete = makeHandleCardUpdate; const makeHandleCardDelete = makeHandleCardCreate;
export default { export default {
getCards, getCards,
-28
View File
@@ -1,28 +0,0 @@
import socket from './socket';
import { transformAction } from './actions';
/* Actions */
const createCommentAction = (cardId, data) =>
socket.post(`/cards/${cardId}/comment-actions`, data).then((body) => ({
...body,
item: transformAction(body.item),
}));
const updateCommentAction = (id, data) =>
socket.patch(`/comment-actions/${id}`, data).then((body) => ({
...body,
item: transformAction(body.item),
}));
const deleteCommentAction = (id) =>
socket.delete(`/comment-actions/${id}`).then((body) => ({
...body,
item: transformAction(body.item),
}));
export default {
createCommentAction,
updateCommentAction,
deleteCommentAction,
};
+28
View File
@@ -0,0 +1,28 @@
import socket from './socket';
import { transformActivity } from './activities';
/* Actions */
const createCommentActivity = (cardId, data, headers) =>
socket.post(`/cards/${cardId}/comment-actions`, data, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
const updateCommentActivity = (id, data, headers) =>
socket.patch(`/comment-actions/${id}`, data, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
const deleteCommentActivity = (id, headers) =>
socket.delete(`/comment-actions/${id}`, undefined, headers).then((body) => ({
...body,
item: transformActivity(body.item),
}));
export default {
createCommentActivity,
updateCommentActivity,
deleteCommentActivity,
};
+2 -2
View File
@@ -6,7 +6,7 @@ const http = {};
// TODO: add all methods // TODO: add all methods
['POST'].forEach((method) => { ['POST'].forEach((method) => {
http[method.toLowerCase()] = (url, data) => { http[method.toLowerCase()] = (url, data, headers) => {
const formData = Object.keys(data).reduce((result, key) => { const formData = Object.keys(data).reduce((result, key) => {
result.append(key, data[key]); result.append(key, data[key]);
@@ -15,8 +15,8 @@ const http = {};
return fetch(`${Config.SERVER_BASE_URL}/api${url}`, { return fetch(`${Config.SERVER_BASE_URL}/api${url}`, {
method, method,
headers,
body: formData, body: formData,
...Config.FETCH_OPTIONS,
}) })
.then((response) => .then((response) =>
response.json().then((body) => ({ response.json().then((body) => ({
+4 -4
View File
@@ -13,8 +13,8 @@ import cardMemberships from './card-memberships';
import cardLabels from './card-labels'; import cardLabels from './card-labels';
import tasks from './tasks'; import tasks from './tasks';
import attachments from './attachments'; import attachments from './attachments';
import actions from './actions'; import activities from './activities';
import commentActions from './comment-actions'; import commentActivities from './comment-activities';
import notifications from './notifications'; import notifications from './notifications';
export { http, socket }; export { http, socket };
@@ -33,7 +33,7 @@ export default {
...cardLabels, ...cardLabels,
...tasks, ...tasks,
...attachments, ...attachments,
...actions, ...activities,
...commentActions, ...commentActivities,
...notifications, ...notifications,
}; };
+4 -3
View File
@@ -2,11 +2,12 @@ import socket from './socket';
/* Actions */ /* Actions */
const createLabel = (boardId, data) => socket.post(`/boards/${boardId}/labels`, data); const createLabel = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/labels`, data, headers);
const updateLabel = (id, data) => socket.patch(`/labels/${id}`, data); const updateLabel = (id, data, headers) => socket.patch(`/labels/${id}`, data, headers);
const deleteLabel = (id) => socket.delete(`/labels/${id}`); const deleteLabel = (id, headers) => socket.delete(`/labels/${id}`, undefined, headers);
export default { export default {
createLabel, createLabel,
+4 -3
View File
@@ -2,11 +2,12 @@ import socket from './socket';
/* Actions */ /* Actions */
const createList = (boardId, data) => socket.post(`/boards/${boardId}/lists`, data); const createList = (boardId, data, headers) =>
socket.post(`/boards/${boardId}/lists`, data, headers);
const updateList = (id, data) => socket.patch(`/lists/${id}`, data); const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);
const deleteList = (id) => socket.delete(`/lists/${id}`); const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
export default { export default {
createList, createList,
+38 -10
View File
@@ -1,33 +1,61 @@
import omit from 'lodash/omit';
import socket from './socket'; import socket from './socket';
import { transformCard } from './cards'; import { transformCard } from './cards';
import { transformAction } from './actions'; import { transformActivity } from './activities';
/* Transformers */
export const transformNotification = (notification) => ({
...omit(notification, 'actionId'),
activityId: notification.actionId,
});
/* Actions */ /* Actions */
const getNotifications = () => const getNotifications = (headers) =>
socket.get('/notifications').then((body) => ({ socket.get('/notifications', undefined, headers).then((body) => ({
...body, ...body,
items: body.items.map(transformNotification),
included: { included: {
...body.included, ...omit(body.included, 'actions'),
cards: body.included.cards.map(transformCard), cards: body.included.cards.map(transformCard),
actions: body.included.actions.map(transformAction), activities: body.included.actions.map(transformActivity),
}, },
})); }));
const getNotification = (id) => const getNotification = (id, headers) =>
socket.get(`/notifications/${id}`).then((body) => ({ socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({
...body, ...body,
item: transformNotification(body.item),
included: { included: {
...body.included, ...omit(body.included, 'actions'),
cards: body.included.cards.map(transformCard), cards: body.included.cards.map(transformCard),
actions: body.included.actions.map(transformAction), activities: body.included.actions.map(transformActivity),
}, },
})); }));
const updateNotifications = (ids, data) => socket.patch(`/notifications/${ids.join(',')}`, data); const updateNotifications = (ids, data, headers) =>
socket.patch(`/notifications/${ids.join(',')}`, data, headers).then((body) => ({
...body,
items: body.items.map(transformNotification),
}));
/* Event handlers */
const makeHandleNotificationCreate = (next) => (body) => {
next({
...body,
item: transformNotification(body.item),
});
};
const makeHandleNotificationUpdate = makeHandleNotificationCreate;
export default { export default {
getNotifications, getNotifications,
getNotification, getNotification,
updateNotifications, updateNotifications,
makeHandleNotificationCreate,
makeHandleNotificationUpdate,
}; };
+4 -3
View File
@@ -2,10 +2,11 @@ import socket from './socket';
/* Actions */ /* Actions */
const createProjectManager = (projectId, data) => const createProjectManager = (projectId, data, headers) =>
socket.post(`/projects/${projectId}/managers`, data); socket.post(`/projects/${projectId}/managers`, data, headers);
const deleteProjectManager = (id) => socket.delete(`/project-managers/${id}`); const deleteProjectManager = (id, headers) =>
socket.delete(`/project-managers/${id}`, undefined, headers);
export default { export default {
createProjectManager, createProjectManager,
+7 -7
View File
@@ -3,18 +3,18 @@ import socket from './socket';
/* Actions */ /* Actions */
const getProjects = () => socket.get('/projects'); const getProjects = (headers) => socket.get('/projects', undefined, headers);
const createProject = (data) => socket.post('/projects', data); const createProject = (data, headers) => socket.post('/projects', data, headers);
const getProject = (id) => socket.get(`/projects/${id}`); const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
const updateProject = (id, data) => socket.patch(`/projects/${id}`, data); const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
const updateProjectBackgroundImage = (id, data) => const updateProjectBackgroundImage = (id, data, headers) =>
http.post(`/projects/${id}/background-image`, data); http.post(`/projects/${id}/background-image`, data, headers);
const deleteProject = (id) => socket.delete(`/projects/${id}`); const deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefined, headers);
export default { export default {
getProjects, getProjects,
+2 -1
View File
@@ -16,12 +16,13 @@ const { socket } = io;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => { ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
socket[method.toLowerCase()] = (url, data) => socket[method.toLowerCase()] = (url, data, headers) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
socket.request( socket.request(
{ {
method, method,
data, data,
headers,
url: `/api${url}`, url: `/api${url}`,
}, },
(_, { body, error }) => { (_, { body, error }) => {
+3 -3
View File
@@ -2,11 +2,11 @@ import socket from './socket';
/* Actions */ /* Actions */
const createTask = (cardId, data) => socket.post(`/cards/${cardId}/tasks`, data); const createTask = (cardId, data, headers) => socket.post(`/cards/${cardId}/tasks`, data, headers);
const updateTask = (id, data) => socket.patch(`/tasks/${id}`, data); const updateTask = (id, data, headers) => socket.patch(`/tasks/${id}`, data, headers);
const deleteTask = (id) => socket.delete(`/tasks/${id}`); const deleteTask = (id, headers) => socket.delete(`/tasks/${id}`, undefined, headers);
export default { export default {
createTask, createTask,
+12 -10
View File
@@ -3,25 +3,27 @@ import socket from './socket';
/* Actions */ /* Actions */
const getUsers = () => socket.get('/users'); const getUsers = (headers) => socket.get('/users', undefined, headers);
const createUser = (data) => socket.post('/users', data); const createUser = (data, headers) => socket.post('/users', data, headers);
const getUser = (id) => socket.get(`/users/${id}`); const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
const getCurrentUser = () => socket.get('/users/me'); const getCurrentUser = (headers) => socket.get('/users/me', undefined, headers);
const updateUser = (id, data) => socket.patch(`/users/${id}`, data); const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
const updateUserEmail = (id, data) => socket.patch(`/users/${id}/email`, data); const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
const updateUserPassword = (id, data) => socket.patch(`/users/${id}/password`, data); const updateUserPassword = (id, data, headers) =>
socket.patch(`/users/${id}/password`, data, headers);
const updateUserUsername = (id, data) => socket.patch(`/users/${id}/username`, data); const updateUserUsername = (id, data, headers) =>
socket.patch(`/users/${id}/username`, data, headers);
const updateUserAvatar = (id, data) => http.post(`/users/${id}/avatar`, data); const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
const deleteUser = (id) => socket.delete(`/users/${id}`); const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
export default { export default {
getUsers, getUsers,
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import Filters from './Filters'; import Filters from './Filters';
import Memberships from '../Memberships'; import Memberships from '../Memberships';
import BoardMembershipPermissionsSelectStep from '../BoardMembershipPermissionsSelectStep';
import styles from './BoardActions.module.scss'; import styles from './BoardActions.module.scss';
@@ -15,6 +16,7 @@ const BoardActions = React.memo(
allUsers, allUsers,
canEditMemberships, canEditMemberships,
onMembershipCreate, onMembershipCreate,
onMembershipUpdate,
onMembershipDelete, onMembershipDelete,
onUserToFilterAdd, onUserToFilterAdd,
onUserFromFilterRemove, onUserFromFilterRemove,
@@ -30,8 +32,10 @@ const BoardActions = React.memo(
<Memberships <Memberships
items={memberships} items={memberships}
allUsers={allUsers} allUsers={allUsers}
permissionsSelectStep={BoardMembershipPermissionsSelectStep}
canEdit={canEditMemberships} canEdit={canEditMemberships}
onCreate={onMembershipCreate} onCreate={onMembershipCreate}
onUpdate={onMembershipUpdate}
onDelete={onMembershipDelete} onDelete={onMembershipDelete}
/> />
</div> </div>
@@ -65,6 +69,7 @@ BoardActions.propTypes = {
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
canEditMemberships: PropTypes.bool.isRequired, canEditMemberships: PropTypes.bool.isRequired,
onMembershipCreate: PropTypes.func.isRequired, onMembershipCreate: PropTypes.func.isRequired,
onMembershipUpdate: PropTypes.func.isRequired,
onMembershipDelete: PropTypes.func.isRequired, onMembershipDelete: PropTypes.func.isRequired,
onUserToFilterAdd: PropTypes.func.isRequired, onUserToFilterAdd: PropTypes.func.isRequired,
onUserFromFilterRemove: PropTypes.func.isRequired, onUserFromFilterRemove: PropTypes.func.isRequired,
@@ -15,7 +15,7 @@ const DEFAULT_DATA = {
const ListAdd = React.memo(({ onCreate, onClose }) => { const ListAdd = React.memo(({ onCreate, onClose }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA); const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [selectNameFieldState, selectNameField] = useToggle(); const [focusNameFieldState, focusNameField] = useToggle();
const nameField = useRef(null); const nameField = useRef(null);
@@ -44,16 +44,16 @@ const ListAdd = React.memo(({ onCreate, onClose }) => {
onCreate(cleanData); onCreate(cleanData);
setData(DEFAULT_DATA); setData(DEFAULT_DATA);
selectNameField(); focusNameField();
}, [onCreate, data, setData, selectNameField]); }, [onCreate, data, setData, focusNameField]);
useEffect(() => { useEffect(() => {
nameField.current.select(); nameField.current.focus();
}, []); }, []);
useDidUpdate(() => { useDidUpdate(() => {
nameField.current.select(); nameField.current.focus();
}, [selectNameFieldState]); }, [focusNameFieldState]);
return ( return (
<Form className={styles.wrapper} onSubmit={handleSubmit}> <Form className={styles.wrapper} onSubmit={handleSubmit}>
@@ -0,0 +1,101 @@
import omit from 'lodash/omit';
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Menu, Radio, Segment } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { BoardMembershipRoles } from '../../constants/Enums';
import styles from './BoardMembershipPermissionsSelectStep.module.scss';
const BoardMembershipPermissionsSelectStep = React.memo(
({ defaultData, title, buttonContent, onSelect, onBack }) => {
const [t] = useTranslation();
const [data, setData] = useState(() => ({
role: BoardMembershipRoles.EDITOR,
canComment: null,
...defaultData,
}));
const handleRoleSelectClick = useCallback((role) => {
setData((prevData) => ({
...prevData,
role,
canComment: role === BoardMembershipRoles.EDITOR ? null : !!prevData.canComment,
}));
}, []);
const handleSettingChange = useCallback((_, { name: fieldName, checked: value }) => {
setData((prevData) => ({
...prevData,
[fieldName]: value,
}));
}, []);
const handleSubmit = useCallback(() => {
onSelect(data.role === BoardMembershipRoles.EDITOR ? omit(data, 'canComment') : data);
}, [onSelect, data]);
return (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Form onSubmit={handleSubmit}>
<Menu secondary vertical className={styles.menu}>
<Menu.Item
active={data.role === BoardMembershipRoles.EDITOR}
onClick={() => handleRoleSelectClick(BoardMembershipRoles.EDITOR)}
>
<div className={styles.menuItemTitle}>{t('common.editor')}</div>
<div className={styles.menuItemDescription}>
{t('common.canEditContentOfBoard')}
</div>
</Menu.Item>
<Menu.Item
active={data.role === BoardMembershipRoles.VIEWER}
onClick={() => handleRoleSelectClick(BoardMembershipRoles.VIEWER)}
>
<div className={styles.menuItemTitle}>{t('common.viewer')}</div>
<div className={styles.menuItemDescription}>{t('common.canOnlyViewBoard')}</div>
</Menu.Item>
</Menu>
{data.role !== BoardMembershipRoles.EDITOR && (
<Segment basic className={styles.settings}>
<Radio
toggle
name="canComment"
checked={data.canComment}
label={t('common.canComment')}
onChange={handleSettingChange}
/>
</Segment>
)}
<Button positive content={t(buttonContent)} />
</Form>
</Popup.Content>
</>
);
},
);
BoardMembershipPermissionsSelectStep.propTypes = {
defaultData: PropTypes.object, // eslint-disable-line react/forbid-prop-types
title: PropTypes.string,
buttonContent: PropTypes.string,
onSelect: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
};
BoardMembershipPermissionsSelectStep.defaultProps = {
defaultData: undefined,
title: 'common.selectPermissions',
buttonContent: 'action.selectPermissions',
};
export default BoardMembershipPermissionsSelectStep;
@@ -0,0 +1,18 @@
:global(#app) {
.menu {
margin: 0 auto 8px;
width: 100%;
}
.menuItemDescription {
opacity: 0.5;
}
.menuItemTitle {
margin-bottom: 8px;
}
.settings {
margin: 0 0 8px;
}
}
@@ -0,0 +1,3 @@
import BoardMembershipPermissionsSelectStep from './BoardMembershipPermissionsSelectStep';
export default BoardMembershipPermissionsSelectStep;
@@ -1,9 +1,10 @@
import React, { useCallback } from 'react'; import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react'; import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui'; import { Input, Popup } from '../../lib/custom-ui';
import { useField } from '../../hooks';
import Item from './Item'; import Item from './Item';
import styles from './BoardMembershipsStep.module.scss'; import styles from './BoardMembershipsStep.module.scss';
@@ -11,6 +12,21 @@ import styles from './BoardMembershipsStep.module.scss';
const BoardMembershipsStep = React.memo( const BoardMembershipsStep = React.memo(
({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => { ({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [search, handleSearchChange] = useField('');
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
const filteredItems = useMemo(
() =>
items.filter(
({ user }) =>
user.email.includes(cleanSearch) ||
user.name.toLowerCase().includes(cleanSearch) ||
(user.username && user.username.includes(cleanSearch)),
),
[items, cleanSearch],
);
const searchField = useRef(null);
const handleUserSelect = useCallback( const handleUserSelect = useCallback(
(id) => { (id) => {
@@ -26,12 +42,29 @@ const BoardMembershipsStep = React.memo(
[onUserDeselect], [onUserDeselect],
); );
useEffect(() => {
searchField.current.focus();
}, []);
return ( return (
<> <>
<Popup.Header onBack={onBack}>{t(title)}</Popup.Header> <Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content> <Popup.Content>
<Input
fluid
ref={searchField}
value={search}
placeholder={t('common.searchMembers')}
icon="search"
onChange={handleSearchChange}
/>
{filteredItems.length > 0 && (
<Menu secondary vertical className={styles.menu}> <Menu secondary vertical className={styles.menu}>
{items.map((item) => ( {filteredItems.map((item) => (
<Item <Item
key={item.id} key={item.id}
isPersisted={item.isPersisted} isPersisted={item.isPersisted}
@@ -42,6 +75,7 @@ const BoardMembershipsStep = React.memo(
/> />
))} ))}
</Menu> </Menu>
)}
</Popup.Content> </Popup.Content>
</> </>
); );
@@ -1,6 +1,22 @@
:global(#app) { :global(#app) {
.menu { .menu {
margin: -7px auto -5px; margin: 8px auto 0;
max-height: 320px;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
width: 100%; width: 100%;
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
}
} }
} }
@@ -2,6 +2,7 @@
.menuItem { .menuItem {
display: block; display: block;
margin: 0; margin: 0;
overflow: hidden;
padding: 4px; padding: 4px;
} }
+1 -1
View File
@@ -35,7 +35,7 @@ const AddStep = React.memo(({ onCreate, onClose }) => {
}, [onCreate, onClose, data]); }, [onCreate, onClose, data]);
useEffect(() => { useEffect(() => {
nameField.current.select(); nameField.current.focus();
}, []); }, []);
return ( return (
@@ -77,6 +77,7 @@
.cover { .cover {
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
margin-bottom: 2px;
vertical-align: middle; vertical-align: middle;
} }
@@ -114,6 +115,7 @@
} }
.wrapper { .wrapper {
cursor: auto;
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
} }
+1 -1
View File
@@ -79,7 +79,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.select(); field.current.ref.current.focus();
} }
}, [isOpened]); }, [isOpened]);
@@ -1,105 +0,0 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Comment, Icon, Loader, Visibility } from 'semantic-ui-react';
import { ActionTypes } from '../../../constants/Enums';
import CommentAdd from './CommentAdd';
import Item from './Item';
import styles from './Actions.module.scss';
const Actions = React.memo(
({
items,
isFetching,
isAllFetched,
canEdit,
canEditAllComments,
onFetch,
onCommentCreate,
onCommentUpdate,
onCommentDelete,
}) => {
const [t] = useTranslation();
const handleCommentUpdate = useCallback(
(id, data) => {
onCommentUpdate(id, data);
},
[onCommentUpdate],
);
const handleCommentDelete = useCallback(
(id) => {
onCommentDelete(id);
},
[onCommentDelete],
);
return (
<>
{canEdit && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="comment outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.addComment')}</div>
<CommentAdd onCreate={onCommentCreate} />
</div>
</div>
)}
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="list ul" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.actions')}</div>
<div className={styles.wrapper}>
<Comment.Group>
{items.map((item) =>
item.type === ActionTypes.COMMENT_CARD ? (
<Item.Comment
key={item.id}
data={item.data}
createdAt={item.createdAt}
isPersisted={item.isPersisted}
user={item.user}
canEdit={(item.user.isCurrent && canEdit) || canEditAllComments}
onUpdate={(data) => handleCommentUpdate(item.id, data)}
onDelete={() => handleCommentDelete(item.id)}
/>
) : (
<Item
key={item.id}
type={item.type}
data={item.data}
createdAt={item.createdAt}
user={item.user}
/>
),
)}
</Comment.Group>
</div>
{isFetching ? (
<Loader active inverted inline="centered" size="small" className={styles.loader} />
) : (
!isAllFetched && <Visibility fireOnMount onOnScreen={onFetch} />
)}
</div>
</div>
</>
);
},
);
Actions.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isFetching: PropTypes.bool.isRequired,
isAllFetched: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
canEditAllComments: PropTypes.bool.isRequired,
onFetch: PropTypes.func.isRequired,
onCommentCreate: PropTypes.func.isRequired,
onCommentUpdate: PropTypes.func.isRequired,
onCommentDelete: PropTypes.func.isRequired,
};
export default Actions;
@@ -1,3 +0,0 @@
import Actions from './Actions';
export default Actions;
+112
View File
@@ -0,0 +1,112 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Comment, Icon, Loader, Visibility } from 'semantic-ui-react';
import { ActivityTypes } from '../../../constants/Enums';
import CommentAdd from './CommentAdd';
import Item from './Item';
import styles from './Activities.module.scss';
const Activities = React.memo(
({
items,
isFetching,
isAllFetched,
isDetailsVisible,
isDetailsFetching,
canEdit,
canEditAllComments,
onFetch,
onDetailsToggle,
onCommentCreate,
onCommentUpdate,
onCommentDelete,
}) => {
const [t] = useTranslation();
const handleToggleDetailsClick = useCallback(() => {
onDetailsToggle(!isDetailsVisible);
}, [isDetailsVisible, onDetailsToggle]);
const handleCommentUpdate = useCallback(
(id, data) => {
onCommentUpdate(id, data);
},
[onCommentUpdate],
);
const handleCommentDelete = useCallback(
(id) => {
onCommentDelete(id);
},
[onCommentDelete],
);
return (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="list ul" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>
{t('common.actions')}
<Button
content={isDetailsVisible ? t('action.hideDetails') : t('action.showDetails')}
className={styles.toggleButton}
onClick={handleToggleDetailsClick}
/>
</div>
{canEdit && <CommentAdd onCreate={onCommentCreate} />}
<div className={styles.wrapper}>
<Comment.Group>
{items.map((item) =>
item.type === ActivityTypes.COMMENT_CARD ? (
<Item.Comment
key={item.id}
data={item.data}
createdAt={item.createdAt}
isPersisted={item.isPersisted}
user={item.user}
canEdit={(item.user.isCurrent && canEdit) || canEditAllComments}
onUpdate={(data) => handleCommentUpdate(item.id, data)}
onDelete={() => handleCommentDelete(item.id)}
/>
) : (
<Item
key={item.id}
type={item.type}
data={item.data}
createdAt={item.createdAt}
user={item.user}
/>
),
)}
</Comment.Group>
</div>
{isFetching || isDetailsFetching ? (
<Loader active inverted inline="centered" size="small" className={styles.loader} />
) : (
!isAllFetched && <Visibility fireOnMount onOnScreen={onFetch} />
)}
</div>
</div>
);
},
);
Activities.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isFetching: PropTypes.bool.isRequired,
isAllFetched: PropTypes.bool.isRequired,
isDetailsVisible: PropTypes.bool.isRequired,
isDetailsFetching: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
canEditAllComments: PropTypes.bool.isRequired,
onFetch: PropTypes.func.isRequired,
onDetailsToggle: PropTypes.func.isRequired,
onCommentCreate: PropTypes.func.isRequired,
onCommentUpdate: PropTypes.func.isRequired,
onCommentDelete: PropTypes.func.isRequired,
};
export default Activities;
@@ -8,12 +8,14 @@
} }
.moduleHeader { .moduleHeader {
align-items: center;
color: #17394d; color: #17394d;
display: flex;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: 20px; height: 36px;
justify-content: space-between;
margin: 0 0 4px; margin: 0 0 4px;
padding: 8px 0;
} }
.moduleIcon { .moduleIcon {
@@ -33,6 +35,24 @@
position: relative; position: relative;
} }
.toggleButton {
background: transparent;
box-shadow: none;
color: #6b808c;
float: right;
font-weight: normal;
margin-right: 0;
padding: 6px 11px;
text-align: left;
text-decoration: underline;
transition: none;
&:hover {
background: rgba(9, 30, 66, 0.08);
color: #092d42;
}
}
.wrapper { .wrapper {
margin-left: -40px; margin-left: -40px;
margin-top: 12px; margin-top: 12px;
@@ -1,10 +1,11 @@
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react'; import { Button, Form, TextArea } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../../lib/hooks';
import { useForm } from '../../../hooks'; import { useClosableForm, useForm } from '../../../hooks';
import styles from './CommentAdd.module.scss'; import styles from './CommentAdd.module.scss';
@@ -14,10 +15,16 @@ const DEFAULT_DATA = {
const CommentAdd = React.memo(({ onCreate }) => { const CommentAdd = React.memo(({ onCreate }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA); const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [selectTextFieldState, selectTextField] = useToggle();
const textField = useRef(null); const textField = useRef(null);
const close = useCallback(() => {
setIsOpened(false);
}, []);
const submit = useCallback(() => { const submit = useCallback(() => {
const cleanData = { const cleanData = {
...data, ...data,
@@ -32,7 +39,12 @@ const CommentAdd = React.memo(({ onCreate }) => {
onCreate(cleanData); onCreate(cleanData);
setData(DEFAULT_DATA); setData(DEFAULT_DATA);
}, [onCreate, data, setData]); selectTextField();
}, [onCreate, data, setData, selectTextField]);
const handleFieldFocus = useCallback(() => {
setIsOpened(true);
}, []);
const handleFieldKeyDown = useCallback( const handleFieldKeyDown = useCallback(
(event) => { (event) => {
@@ -43,10 +55,16 @@ const CommentAdd = React.memo(({ onCreate }) => {
[submit], [submit],
); );
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(close);
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
submit(); submit();
}, [submit]); }, [submit]);
useDidUpdate(() => {
textField.current.ref.current.focus();
}, [selectTextFieldState]);
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<TextArea <TextArea
@@ -55,15 +73,24 @@ const CommentAdd = React.memo(({ onCreate }) => {
name="text" name="text"
value={data.text} value={data.text}
placeholder={t('common.writeComment')} placeholder={t('common.writeComment')}
minRows={3} minRows={isOpened ? 3 : 1}
spellCheck={false} spellCheck={false}
className={styles.field} className={styles.field}
onFocus={handleFieldFocus}
onKeyDown={handleFieldKeyDown} onKeyDown={handleFieldKeyDown}
onChange={handleFieldChange} onChange={handleFieldChange}
onBlur={handleFieldBlur}
/> />
{isOpened && (
<div className={styles.controls}> <div className={styles.controls}>
<Button positive content={t('action.addComment')} disabled={!data.text} /> <Button
positive
content={t('action.addComment')}
onMouseOver={handleControlMouseOver}
onMouseOut={handleControlMouseOut}
/>
</div> </div>
)}
</Form> </Form>
); );
}); });
@@ -76,7 +76,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
textField.current.ref.current.select(); textField.current.ref.current.focus();
} }
}, [isOpened]); }, [isOpened]);
@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { useTranslation, Trans } from 'react-i18next'; import { useTranslation, Trans } from 'react-i18next';
import { Comment } from 'semantic-ui-react'; import { Comment } from 'semantic-ui-react';
import { ActionTypes } from '../../../constants/Enums'; import { ActivityTypes } from '../../../constants/Enums';
import ItemComment from './ItemComment'; import ItemComment from './ItemComment';
import User from '../../User'; import User from '../../User';
@@ -16,7 +16,7 @@ const Item = React.memo(({ type, data, createdAt, user }) => {
let contentNode; let contentNode;
switch (type) { switch (type) {
case ActionTypes.CREATE_CARD: case ActivityTypes.CREATE_CARD:
contentNode = ( contentNode = (
<Trans <Trans
i18nKey="common.userAddedThisCardToList" i18nKey="common.userAddedThisCardToList"
@@ -34,7 +34,7 @@ const Item = React.memo(({ type, data, createdAt, user }) => {
); );
break; break;
case ActionTypes.MOVE_CARD: case ActivityTypes.MOVE_CARD:
contentNode = ( contentNode = (
<Trans <Trans
i18nKey="common.userMovedThisCardFromListToList" i18nKey="common.userMovedThisCardFromListToList"
+3
View File
@@ -0,0 +1,3 @@
import Activities from './Activities';
export default Activities;
@@ -37,7 +37,7 @@ const TextFileAddModal = React.memo(({ content, onCreate, onClose }) => {
}, [content, onCreate, onClose, data]); }, [content, onCreate, onClose, data]);
useEffect(() => { useEffect(() => {
nameField.current.select(); nameField.current.focus();
}, []); }, []);
return ( return (
@@ -1,6 +1,8 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';
import { Button } from 'semantic-ui-react'; import { Button } from 'semantic-ui-react';
import { useToggle } from '../../../lib/hooks'; import { useToggle } from '../../../lib/hooks';
@@ -8,13 +10,12 @@ import Item from './Item';
import styles from './Attachments.module.scss'; import styles from './Attachments.module.scss';
const Attachments = React.memo(({ items, onUpdate, onDelete, onCoverUpdate }) => { const INITIALLY_VISIBLE = 4;
const [t] = useTranslation();
const [isOpened, toggleOpened] = useToggle();
const handleToggleClick = useCallback(() => { const Attachments = React.memo(
toggleOpened(); ({ items, canEdit, onUpdate, onDelete, onCoverUpdate, onGalleryOpen, onGalleryClose }) => {
}, [toggleOpened]); const [t] = useTranslation();
const [isAllVisible, toggleAllVisible] = useToggle();
const handleCoverSelect = useCallback( const handleCoverSelect = useCallback(
(id) => { (id) => {
@@ -41,48 +42,122 @@ const Attachments = React.memo(({ items, onUpdate, onDelete, onCoverUpdate }) =>
[onDelete], [onDelete],
); );
const visibleItems = isOpened ? items : items.slice(0, 4); const handleBeforeGalleryOpen = useCallback(
(gallery) => {
onGalleryOpen();
gallery.on('destroy', () => {
onGalleryClose();
});
},
[onGalleryOpen, onGalleryClose],
);
const handleToggleAllVisibleClick = useCallback(() => {
toggleAllVisible();
}, [toggleAllVisible]);
const galleryItemsNode = items.map((item, index) => {
const isPdf = item.url && item.url.endsWith('.pdf');
let props;
if (item.image) {
props = item.image;
} else {
props = {
content: isPdf ? (
// eslint-disable-next-line jsx-a11y/alt-text
<object
data={item.url}
type="application/pdf"
className={classNames(styles.content, styles.contentPdf)}
/>
) : (
<span className={classNames(styles.content, styles.contentError)}>
{t('common.thereIsNoPreviewAvailableForThisAttachment')}
</span>
),
};
}
const isVisible = isAllVisible || index < INITIALLY_VISIBLE;
return ( return (
<> <GalleryItem
{visibleItems.map((item) => ( {...props} // eslint-disable-line react/jsx-props-no-spreading
<Item
key={item.id} key={item.id}
original={item.url}
caption={item.name}
>
{({ ref, open }) =>
isVisible ? (
<Item
ref={ref}
name={item.name} name={item.name}
url={item.url} url={item.url}
coverUrl={item.coverUrl} coverUrl={item.coverUrl}
createdAt={item.createdAt} createdAt={item.createdAt}
isCover={item.isCover} isCover={item.isCover}
isPersisted={item.isPersisted} isPersisted={item.isPersisted}
canEdit={canEdit}
onClick={item.image || isPdf ? open : undefined}
onCoverSelect={() => handleCoverSelect(item.id)} onCoverSelect={() => handleCoverSelect(item.id)}
onCoverDeselect={handleCoverDeselect} onCoverDeselect={handleCoverDeselect}
onUpdate={(data) => handleUpdate(item.id, data)} onUpdate={(data) => handleUpdate(item.id, data)}
onDelete={() => handleDelete(item.id)} onDelete={() => handleDelete(item.id)}
/> />
))} ) : (
{items.length > 4 && ( <span ref={ref} />
)
}
</GalleryItem>
);
});
return (
<>
<Gallery
withCaption
withDownloadButton
options={{
showHideAnimationType: 'none',
closeTitle: '',
zoomTitle: '',
arrowPrevTitle: '',
arrowNextTitle: '',
errorMsg: '',
}}
onBeforeOpen={handleBeforeGalleryOpen}
>
{galleryItemsNode}
</Gallery>
{items.length > INITIALLY_VISIBLE && (
<Button <Button
fluid fluid
content={ content={
isOpened isAllVisible
? t('action.showFewerAttachments') ? t('action.showFewerAttachments')
: t('action.showAllAttachments', { : t('action.showAllAttachments', {
hidden: items.length - visibleItems.length, hidden: items.length - INITIALLY_VISIBLE,
}) })
} }
className={styles.toggleButton} className={styles.toggleButton}
onClick={handleToggleClick} onClick={handleToggleAllVisibleClick}
/> />
)} )}
</> </>
); );
}); },
);
Attachments.propTypes = { Attachments.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onCoverUpdate: PropTypes.func.isRequired, onCoverUpdate: PropTypes.func.isRequired,
onGalleryOpen: PropTypes.func.isRequired,
onGalleryClose: PropTypes.func.isRequired,
}; };
export default Attachments; export default Attachments;
@@ -1,4 +1,26 @@
:global(#app) { :global(#app) {
.content {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
}
.contentPdf {
height: 100%;
width: 1120px;
}
.contentError {
color: #fff;
font-size: 20px;
font-weight: 700;
height: 20px;
width: 470px;
}
.toggleButton { .toggleButton {
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
@@ -8,24 +8,33 @@ import EditPopup from './EditPopup';
import styles from './Item.module.scss'; import styles from './Item.module.scss';
const Item = React.memo( const Item = React.forwardRef(
({ (
{
name, name,
url, url,
coverUrl, coverUrl,
createdAt, createdAt,
isCover, isCover,
isPersisted, isPersisted,
canEdit,
onCoverSelect, onCoverSelect,
onCoverDeselect, onCoverDeselect,
onClick,
onUpdate, onUpdate,
onDelete, onDelete,
}) => { },
ref,
) => {
const [t] = useTranslation(); const [t] = useTranslation();
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (onClick) {
onClick();
} else {
window.open(url, '_blank'); window.open(url, '_blank');
}, [url]); }
}, [url, onClick]);
const handleToggleCoverClick = useCallback( const handleToggleCoverClick = useCallback(
(event) => { (event) => {
@@ -54,7 +63,7 @@ const Item = React.memo(
return ( return (
/* eslint-disable jsx-a11y/click-events-have-key-events, /* eslint-disable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */ jsx-a11y/no-static-element-interactions */
<div className={styles.wrapper} onClick={handleClick}> <div ref={ref} className={styles.wrapper} onClick={handleClick}>
{/* eslint-enable jsx-a11y/click-events-have-key-events, {/* eslint-enable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */} jsx-a11y/no-static-element-interactions */}
<div <div
@@ -88,7 +97,7 @@ const Item = React.memo(
value: createdAt, value: createdAt,
})} })}
</span> </span>
{coverUrl && ( {coverUrl && canEdit && (
<span className={styles.options}> <span className={styles.options}>
<button type="button" className={styles.option} onClick={handleToggleCoverClick}> <button type="button" className={styles.option} onClick={handleToggleCoverClick}>
<Icon <Icon
@@ -110,6 +119,7 @@ const Item = React.memo(
</span> </span>
)} )}
</div> </div>
{canEdit && (
<EditPopup <EditPopup
defaultData={{ defaultData={{
name, name,
@@ -121,6 +131,7 @@ const Item = React.memo(
<Icon fitted name="pencil" size="small" /> <Icon fitted name="pencil" size="small" />
</Button> </Button>
</EditPopup> </EditPopup>
)}
</div> </div>
); );
}, },
@@ -133,6 +144,8 @@ Item.propTypes = {
createdAt: PropTypes.instanceOf(Date), createdAt: PropTypes.instanceOf(Date),
isCover: PropTypes.bool.isRequired, isCover: PropTypes.bool.isRequired,
isPersisted: PropTypes.bool.isRequired, isPersisted: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onClick: PropTypes.func,
onCoverSelect: PropTypes.func.isRequired, onCoverSelect: PropTypes.func.isRequired,
onCoverDeselect: PropTypes.func.isRequired, onCoverDeselect: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
@@ -143,6 +156,7 @@ Item.defaultProps = {
url: undefined, url: undefined,
coverUrl: undefined, coverUrl: undefined,
createdAt: undefined, createdAt: undefined,
onClick: undefined,
}; };
export default Item; export default React.memo(Item);
+68 -30
View File
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -11,7 +11,7 @@ import Tasks from './Tasks';
import Attachments from './Attachments'; import Attachments from './Attachments';
import AttachmentAddZone from './AttachmentAddZone'; import AttachmentAddZone from './AttachmentAddZone';
import AttachmentAddPopup from './AttachmentAddPopup'; import AttachmentAddPopup from './AttachmentAddPopup';
import Actions from './Actions'; import Activities from './Activities';
import User from '../User'; import User from '../User';
import Label from '../Label'; import Label from '../Label';
import DueDate from '../DueDate'; import DueDate from '../DueDate';
@@ -32,8 +32,10 @@ const CardModal = React.memo(
dueDate, dueDate,
timer, timer,
isSubscribed, isSubscribed,
isActionsFetching, isActivitiesFetching,
isAllActionsFetched, isAllActivitiesFetched,
isActivitiesDetailsVisible,
isActivitiesDetailsFetching,
listId, listId,
boardId, boardId,
projectId, projectId,
@@ -41,12 +43,13 @@ const CardModal = React.memo(
labels, labels,
tasks, tasks,
attachments, attachments,
actions, activities,
allProjectsToLists, allProjectsToLists,
allBoardMemberships, allBoardMemberships,
allLabels, allLabels,
canEdit, canEdit,
canEditAllCommentActions, canEditCommentActivities,
canEditAllCommentActivities,
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
@@ -61,18 +64,22 @@ const CardModal = React.memo(
onLabelDelete, onLabelDelete,
onTaskCreate, onTaskCreate,
onTaskUpdate, onTaskUpdate,
onTaskMove,
onTaskDelete, onTaskDelete,
onAttachmentCreate, onAttachmentCreate,
onAttachmentUpdate, onAttachmentUpdate,
onAttachmentDelete, onAttachmentDelete,
onActionsFetch, onActivitiesFetch,
onCommentActionCreate, onActivitiesDetailsToggle,
onCommentActionUpdate, onCommentActivityCreate,
onCommentActionDelete, onCommentActivityUpdate,
onCommentActivityDelete,
onClose, onClose,
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const isGalleryOpened = useRef(false);
const handleNameUpdate = useCallback( const handleNameUpdate = useCallback(
(newName) => { (newName) => {
onUpdate({ onUpdate({
@@ -124,6 +131,22 @@ const CardModal = React.memo(
}); });
}, [isSubscribed, onUpdate]); }, [isSubscribed, onUpdate]);
const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true;
}, []);
const handleGalleryClose = useCallback(() => {
isGalleryOpened.current = false;
}, []);
const handleClose = useCallback(() => {
if (isGalleryOpened.current) {
return;
}
onClose();
}, [onClose]);
const userIds = users.map((user) => user.id); const userIds = users.map((user) => user.id);
const labelIds = labels.map((label) => label.id); const labelIds = labels.map((label) => label.id);
@@ -280,7 +303,10 @@ const CardModal = React.memo(
{canEdit ? ( {canEdit ? (
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}> <DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
{description ? ( {description ? (
<button type="button" className={styles.descriptionText}> <button
type="button"
className={classNames(styles.descriptionText, styles.cursorPointer)}
>
<Markdown linkStopPropagation linkTarget="_blank"> <Markdown linkStopPropagation linkTarget="_blank">
{description} {description}
</Markdown> </Markdown>
@@ -313,6 +339,7 @@ const CardModal = React.memo(
canEdit={canEdit} canEdit={canEdit}
onCreate={onTaskCreate} onCreate={onTaskCreate}
onUpdate={onTaskUpdate} onUpdate={onTaskUpdate}
onMove={onTaskMove}
onDelete={onTaskDelete} onDelete={onTaskDelete}
/> />
</div> </div>
@@ -325,23 +352,29 @@ const CardModal = React.memo(
<div className={styles.moduleHeader}>{t('common.attachments')}</div> <div className={styles.moduleHeader}>{t('common.attachments')}</div>
<Attachments <Attachments
items={attachments} items={attachments}
canEdit={canEdit}
onUpdate={onAttachmentUpdate} onUpdate={onAttachmentUpdate}
onDelete={onAttachmentDelete} onDelete={onAttachmentDelete}
onCoverUpdate={handleCoverUpdate} onCoverUpdate={handleCoverUpdate}
onGalleryOpen={handleGalleryOpen}
onGalleryClose={handleGalleryClose}
/> />
</div> </div>
</div> </div>
)} )}
<Actions <Activities
items={actions} items={activities}
isFetching={isActionsFetching} isFetching={isActivitiesFetching}
isAllFetched={isAllActionsFetched} isAllFetched={isAllActivitiesFetched}
canEdit={canEdit} isDetailsVisible={isActivitiesDetailsVisible}
canEditAllComments={canEditAllCommentActions} isDetailsFetching={isActivitiesDetailsFetching}
onFetch={onActionsFetch} canEdit={canEditCommentActivities}
onCommentCreate={onCommentActionCreate} canEditAllComments={canEditAllCommentActivities}
onCommentUpdate={onCommentActionUpdate} onFetch={onActivitiesFetch}
onCommentDelete={onCommentActionDelete} onDetailsToggle={onActivitiesDetailsToggle}
onCommentCreate={onCommentActivityCreate}
onCommentUpdate={onCommentActivityUpdate}
onCommentDelete={onCommentActivityDelete}
/> />
</Grid.Column> </Grid.Column>
{canEdit && ( {canEdit && (
@@ -445,7 +478,7 @@ const CardModal = React.memo(
); );
return ( return (
<Modal open closeIcon size="small" centered={false} onClose={onClose}> <Modal open closeIcon centered={false} onClose={handleClose} className={styles.wrapper}>
{canEdit ? ( {canEdit ? (
<AttachmentAddZone onCreate={onAttachmentCreate}>{contentNode}</AttachmentAddZone> <AttachmentAddZone onCreate={onAttachmentCreate}>{contentNode}</AttachmentAddZone>
) : ( ) : (
@@ -462,8 +495,10 @@ CardModal.propTypes = {
dueDate: PropTypes.instanceOf(Date), dueDate: PropTypes.instanceOf(Date),
timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
isSubscribed: PropTypes.bool.isRequired, isSubscribed: PropTypes.bool.isRequired,
isActionsFetching: PropTypes.bool.isRequired, isActivitiesFetching: PropTypes.bool.isRequired,
isAllActionsFetched: PropTypes.bool.isRequired, isAllActivitiesFetched: PropTypes.bool.isRequired,
isActivitiesDetailsVisible: PropTypes.bool.isRequired,
isActivitiesDetailsFetching: PropTypes.bool.isRequired,
listId: PropTypes.string.isRequired, listId: PropTypes.string.isRequired,
boardId: PropTypes.string.isRequired, boardId: PropTypes.string.isRequired,
projectId: PropTypes.string.isRequired, projectId: PropTypes.string.isRequired,
@@ -472,13 +507,14 @@ CardModal.propTypes = {
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
tasks: PropTypes.array.isRequired, tasks: PropTypes.array.isRequired,
attachments: PropTypes.array.isRequired, attachments: PropTypes.array.isRequired,
actions: PropTypes.array.isRequired, activities: PropTypes.array.isRequired,
allProjectsToLists: PropTypes.array.isRequired, allProjectsToLists: PropTypes.array.isRequired,
allBoardMemberships: PropTypes.array.isRequired, allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired, allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
canEditAllCommentActions: PropTypes.bool.isRequired, canEditCommentActivities: PropTypes.bool.isRequired,
canEditAllCommentActivities: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,
@@ -493,14 +529,16 @@ CardModal.propTypes = {
onLabelDelete: PropTypes.func.isRequired, onLabelDelete: PropTypes.func.isRequired,
onTaskCreate: PropTypes.func.isRequired, onTaskCreate: PropTypes.func.isRequired,
onTaskUpdate: PropTypes.func.isRequired, onTaskUpdate: PropTypes.func.isRequired,
onTaskMove: PropTypes.func.isRequired,
onTaskDelete: PropTypes.func.isRequired, onTaskDelete: PropTypes.func.isRequired,
onAttachmentCreate: PropTypes.func.isRequired, onAttachmentCreate: PropTypes.func.isRequired,
onAttachmentUpdate: PropTypes.func.isRequired, onAttachmentUpdate: PropTypes.func.isRequired,
onAttachmentDelete: PropTypes.func.isRequired, onAttachmentDelete: PropTypes.func.isRequired,
onActionsFetch: PropTypes.func.isRequired, onActivitiesFetch: PropTypes.func.isRequired,
onCommentActionCreate: PropTypes.func.isRequired, onActivitiesDetailsToggle: PropTypes.func.isRequired,
onCommentActionUpdate: PropTypes.func.isRequired, onCommentActivityCreate: PropTypes.func.isRequired,
onCommentActionDelete: PropTypes.func.isRequired, onCommentActivityUpdate: PropTypes.func.isRequired,
onCommentActivityDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
}; };
@@ -64,6 +64,10 @@
padding: 8px 8px 0 16px; padding: 8px 8px 0 16px;
} }
.cursorPointer {
cursor: pointer;
}
.dueDate { .dueDate {
background: rgba(9, 30, 66, 0.04); background: rgba(9, 30, 66, 0.04);
border: none; border: none;
@@ -114,7 +118,6 @@
background: transparent; background: transparent;
border: none; border: none;
color: #17394d; color: #17394d;
cursor: pointer;
font-size: 15px; font-size: 15px;
margin-bottom: 8px; margin-bottom: 8px;
outline: none; outline: none;
@@ -193,4 +196,14 @@
margin: 0 8px 4px 0; margin: 0 8px 4px 0;
text-transform: uppercase; text-transform: uppercase;
} }
.wrapper {
width: 880px;
}
@media (max-width: 926px) {
.wrapper {
width: 95%;
}
}
} }
@@ -66,7 +66,7 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
field.current.ref.current.select(); field.current.ref.current.focus();
} }
}, [isOpened]); }, [isOpened]);
@@ -17,7 +17,7 @@ const Add = React.forwardRef(({ children, onCreate }, ref) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isOpened, setIsOpened] = useState(false); const [isOpened, setIsOpened] = useState(false);
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA); const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [selectNameFieldState, selectNameField] = useToggle(); const [focusNameFieldState, focusNameField] = useToggle();
const nameField = useRef(null); const nameField = useRef(null);
@@ -43,8 +43,8 @@ const Add = React.forwardRef(({ children, onCreate }, ref) => {
onCreate(cleanData); onCreate(cleanData);
setData(DEFAULT_DATA); setData(DEFAULT_DATA);
selectNameField(); focusNameField();
}, [onCreate, data, setData, selectNameField]); }, [onCreate, data, setData, focusNameField]);
useImperativeHandle( useImperativeHandle(
ref, ref,
@@ -81,13 +81,13 @@ const Add = React.forwardRef(({ children, onCreate }, ref) => {
useEffect(() => { useEffect(() => {
if (isOpened) { if (isOpened) {
nameField.current.ref.current.select(); nameField.current.ref.current.focus();
} }
}, [isOpened]); }, [isOpened]);
useDidUpdate(() => { useDidUpdate(() => {
nameField.current.ref.current.select(); nameField.current.ref.current.focus();
}, [selectNameFieldState]); }, [focusNameFieldState]);
if (!isOpened) { if (!isOpened) {
return React.cloneElement(children, { return React.cloneElement(children, {

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