Compare commits

..
164 Commits
Author SHA1 Message Date
Maksim Eltyshev 36e4bef21b feat: Remove attachments from public access
Closes #219
2022-04-26 22:20:20 +05:00
Maksim Eltyshev 486e663a3d feat: Store accessToken in cookies instead of localStorage 2022-04-26 18:01:55 +05:00
Michael Fürnschuß 536064fdfe fix: Fix missed table name (#227) 2022-04-25 16:00:14 +05:00
Michael Fürnschuß f03bb5a78f feat: Update German translation (#228) 2022-04-25 15:45:24 +05:00
Maksim Eltyshev 8bf85e2eb6 ref: Rename .env file to sample, stop tracking .env file 2022-04-22 16:21:09 +05:00
Maksim Eltyshev e3d1d36857 fix: Fix markdown options and styles 2022-04-22 02:06:12 +05:00
Maksim Eltyshev 4371e16c8d feat: Fallback to English if translation is missing
Closes #175
2022-04-21 04:42:02 +05:00
Maksim Eltyshev ec7b4d7b24 ref: Fix locale file names 2022-04-21 04:20:04 +05:00
Maksim Eltyshev 7974c9dc5d docs: Add contributing guidelines 2022-04-20 19:36:24 +05:00
Michal VOLF e2a8f5b483 feat: Update Czech translation (#217) 2022-04-20 14:17:00 +05:00
Arcady "RKD" Chumachenko 5049034ae3 feat: Alway apply migrations on start-up (#172) 2022-04-20 04:52:42 +05:00
Maksim Eltyshev 16a822cc7d Prepare for release 2022-04-19 20:36:10 +05:00
Maksim Eltyshev 6f219e5368 Fix pre-commit hook, update dependencies 2022-04-19 02:52:19 +05:00
Maksim Eltyshev e239d85901 Update dependencies 2022-04-16 00:08:14 +05:00
Maksim Eltyshev f539e7b76d Fix language loading. Closes #207 2022-03-31 02:23:44 +05:00
Maksim Eltyshev 0c92623109 Update dependencies 2022-02-09 00:56:01 +05:00
Lorenz Brun 95ea1e97d3 Improve Docker cache efficiency and fix signal handling 2022-02-08 16:15:52 +05:00
Maksim Eltyshev 6f99d99683 Add date-fns locale for Danish language 2022-01-30 02:50:13 +05:00
Christiansen afdcd11da6 Add Danish translation 2022-01-30 02:46:41 +05:00
Maksim Eltyshev a628f116a7 Fix linting errors, fix default database url. Closes #193 2021-12-30 00:30:53 +05:00
Asliddinbek Azizovich 6b158f0f1d Add Uzbek language 2021-12-15 16:16:20 +05:00
RokkuCode f59981db79 Fix several spelling errors and grammar in German translation 2021-12-15 16:11:10 +05:00
MoaufmKlo 96fa39e830 Correct incorrect gender in German translation 2021-12-15 16:04:36 +05:00
MoaufmKlo 2efc20cc02 Improve German translation 2021-10-21 01:39:21 +05:00
SimonTagne 31d314736a Fix updates of username and email 2021-10-21 01:25:04 +05:00
dbrennand 729e5dac5e Make trustProxy configurable via environment variable 2021-10-21 01:24:19 +05:00
ejo090 07e58ffe0c Add Japanese language 2021-10-21 01:22:28 +05:00
Maksim Eltyshev 32aae89c6e Merge branch 'master' of https://github.com/plankanban/planka 2021-08-02 17:24:54 +05:00
Maksim Eltyshev 1a5c0f53f8 Fix board deletion, update dependencies. Closes #146 2021-08-02 17:23:56 +05:00
SohnyBohny b63f0742ba Fix invalid white-spaces in card description. Closes #141 2021-07-12 19:16:54 +05:00
Maksim Eltyshev b39119ace4 Project managers, board members, auto-update after reconnection, refactoring 2021-06-24 01:05:22 +05:00
William Puckering d6cb1f6683 Fix misspelling of «management». Closes #120 2021-04-30 20:26:19 +05:00
Maksim Eltyshev 0f0ae5f483 Add date-fns locale for Spanish language 2021-04-28 00:01:35 +05:00
Santiago Forero f45b5c704f Add Spanish language 2021-04-27 23:53:18 +05:00
Alfred Feldmeyer f123254ea3 Ignore custom docker-compose overrides 2021-04-13 19:08:30 +05:00
Jan Metzger f6dbba2ba9 Fix project title word break on projects overview page 2021-04-13 19:03:23 +05:00
Maksim Eltyshev 0fb60eb886 Allow dots in username. Closes #116 2021-04-13 18:59:02 +05:00
Maksim Eltyshev fb42ed460e Set best image quality for all uploads. Closes #114 2021-04-13 18:47:56 +05:00
Michal VOLF 141fc74dbb Fix datepicker language code for Czech language 2021-03-27 17:50:02 +05:00
Maksim Eltyshev 65aba551c3 Add Czech language. Thanks to @michal-volf 2021-03-26 11:19:43 +05:00
Maksim Eltyshev a3d1a8a09a Add support for GitHub Flavored Markdown. Closes #107 2021-03-25 21:32:31 +05:00
Maksim Eltyshev cf21bef9ee Fix file attach from clipboard when copy from browser. Closes #96 2021-03-20 03:33:16 +05:00
nielsboussion def1134566 Add French language 2021-03-20 02:30:45 +05:00
Nick Kelly a3855f8080 Enable Redux DevTools in development mode 2021-02-16 03:14:43 +05:00
Maksim Eltyshev f718464940 Fix variables in update-card, update dependencies. Closes #85 2021-02-10 17:39:41 +05:00
pkrolkgp 83ad2f45e6 Fix typos in polish translation 2021-01-11 11:10:22 +05:00
Maksim Eltyshev 3d6f9cc0e5 Adjust label width, update dependencies. Closes #75 2021-01-09 18:42:16 +05:00
Maksim Eltyshev 2b318fdc7a Add date-fns locale for polish language 2020-12-20 18:45:33 +05:00
pkrolkgp 73c3bf3728 Add polish language 2020-12-20 18:37:55 +05:00
Maksim Eltyshev 179ad3610f Update dependencies 2020-12-18 01:02:28 +05:00
Stefan Mielke 0eb14c34ef Fix german translations 2020-12-13 23:31:10 +05:00
Maksim Eltyshev 3cdb614b57 Fix loading deleted users on actions fetch. Closes #62 2020-12-13 23:17:12 +05:00
Maksim Eltyshev a3adddac6f Add german language. Closes #54 2020-11-30 21:05:40 +05:00
timohzbs 9f6a852da3 Add core.js de locale 2020-11-30 20:43:53 +05:00
timohzbs 0cf0e84ded Add embed.js de locale 2020-11-30 20:43:25 +05:00
Maksim Eltyshev de1f7c7564 Update dependencies 2020-11-20 06:01:42 +05:00
Maksim Eltyshev de7d080910 Add ability to create multi-arch images. Closes #39, closes #47 2020-11-17 17:01:18 +05:00
Maksim Eltyshev 771c443694 Add z-index to popup container. Closes #46 2020-10-21 14:50:15 +05:00
Maksim Eltyshev c28fada7ee Update readme, update dependencies 2020-10-20 16:10:25 +05:00
Maksim Eltyshev 437ac523bc Update readme 2020-10-17 23:49:19 +05:00
Maksim Eltyshev 6584d1cb5d Create FUNDING.yml 2020-10-06 21:12:14 +05:00
Maksim Eltyshev e47697b0c5 Update readme 2020-10-06 21:07:14 +05:00
Maksim Eltyshev cc1358b477 Update readme 2020-10-06 21:04:43 +05:00
Maksim Eltyshev ab020fc324 Update dependencies 2020-09-28 04:40:10 +05:00
Maksim Eltyshev 08aef7c052 Change database adapter to support the new Node. Closes #32 2020-08-31 16:45:22 +05:00
Maksim Eltyshev 767d39586c Add test libs, update dependencies 2020-08-20 15:35:46 +05:00
Maksim Eltyshev 089668ed10 Fix modal type variable. Closes #20 2020-08-13 11:38:47 +05:00
Maksim Eltyshev 8fc6bb2e3e Update dependencies 2020-08-11 20:45:35 +05:00
Maksim Eltyshev c6ee7d54bb Prepare for collection board type, refactoring, update dependencies 2020-08-04 01:32:46 +05:00
Maksim Eltyshev 2d92ade8dc Add scrollbar for board tabs, update dependencies 2020-06-08 18:49:40 +05:00
Maksim Eltyshev c4e3c3d4c8 Return text-shadow back, make project wrapper less transparent 2020-06-04 21:16:41 +05:00
Maksim Eltyshev d22580cec0 Fix removing background 2020-06-04 17:52:30 +05:00
Maksim Eltyshev 99d9ab4a9e Add validation to background field 2020-06-03 22:27:20 +05:00
Maksim Eltyshev ab83d7c9ab Fix repository language 2020-05-29 21:46:08 +05:00
Maksim Eltyshev ff95a12578 Background gradients, migrate from CSS to SCSS, remove !important 2020-05-29 19:31:19 +05:00
Maksim Eltyshev 196121bd38 Update readme 2020-05-27 15:20:00 +05:00
Maksim Eltyshev 313f7ab7c7 Remove text-shadow everywhere 2020-05-27 12:50:37 +05:00
Maksim Eltyshev 38f68c1b82 Update dependencies 2020-05-26 14:10:06 +05:00
Maksim Eltyshev 99807c2a49 Fix Docker volumes 2020-05-26 01:32:11 +05:00
Maksim Eltyshev 2f7a244807 Add project backgrounds 2020-05-26 00:46:04 +05:00
Maksim Eltyshev fb9ceb5db7 Fix border in card modal 2020-05-23 18:05:22 +05:00
Maksim Eltyshev efd4de4000 Transparent color scheme, preparing for custom backgrounds 2020-05-22 16:59:22 +05:00
Maksim Eltyshev 746e2fe790 Make columns itself scrollable, fix action creation when moving card, little refactoring 2020-05-16 04:09:46 +05:00
Maksim Eltyshev c5b44598f9 Fix members and labels creation when transfer card to another board 2020-05-10 02:06:14 +05:00
Maksim Eltyshev 35f9631326 Preserve members and labels when transfer card to another board 2020-05-09 05:30:52 +05:00
Maksim Eltyshev ca6efd5b71 Set default Postgres user, delete user creation information from readme 2020-05-06 22:37:45 +05:00
Maksim Eltyshev 6a7ad7a24b Update dependencies 2020-05-06 21:21:18 +05:00
Maksim Eltyshev 0519df4e4d Merge pull request #12 from wheresvic/master
Add dev documentation and local db server
2020-05-06 19:07:23 +05:00
Victor Parmar deec1a06a2 c add dev documentation and local db server 2020-05-06 00:07:23 +02:00
Maksim Eltyshev f4c8bc6ab9 Remove members and labels when transfer card to another board 2020-05-05 03:33:29 +05:00
Maksim Eltyshev cbc942e306 Move cards between boards and projects 2020-05-05 01:30:06 +05:00
Maksim Eltyshev 712567ac57 Change user size on card, save card description on click outside, prevent list deletion if any filter is active 2020-04-30 05:27:46 +05:00
Maksim Eltyshev a9f2948672 Add attachment popup, hide attachments if more than 4 2020-04-29 23:55:16 +05:00
Maksim Eltyshev 844b3ab8f1 Fix Docker image, update dependencies 2020-04-29 04:50:32 +05:00
Maksim Eltyshev 246eb17249 Add dropzone for attachment, paste attachment from clipboard 2020-04-28 19:46:55 +05:00
Maksim Eltyshev e1364925ed Link all scripts to the root package.json, start development servers concurrently 2020-04-25 04:57:31 +05:00
Maksim Eltyshev 5481ccb91f Add start script for Docker image, pre-commit lint check, package.json scripts cleanup 2020-04-24 21:44:54 +05:00
Maksim Eltyshev 73bd7aab89 Fix path to Docker Hub 2020-04-23 23:03:40 +05:00
Maksim Eltyshev 63807c7db6 Update repository paths because of transfer 2020-04-23 22:56:25 +05:00
Maksim Eltyshev ce39b584ca Fix attachment duplication on add 2020-04-23 05:56:02 +05:00
Maksim Eltyshev 3e85371da9 Fix attachment thumbnail label color 2020-04-23 03:49:39 +05:00
Maksim Eltyshev 3dffed90c6 Add covers for cards 2020-04-23 03:02:53 +05:00
Maksim Eltyshev f743f4ea8b Add file attachments 2020-04-21 05:44:12 +05:00
Maksim Eltyshev 87a3cf751a Fix language detection for some browsers 2020-04-11 01:02:08 +05:00
Maksim Eltyshev be8a806102 Update dependencies 2020-04-10 00:24:05 +05:00
Maksim Eltyshev 88314e826d Add preferences tab to user settings, add subscribe to own cards option 2020-04-10 00:11:34 +05:00
Maksim Eltyshev 9b4e3931a9 Add phone and organization fields to user 2020-04-09 18:27:28 +05:00
Maksim Eltyshev c6ecf126d0 Add user settings modal 2020-04-08 21:12:58 +05:00
Maksim Eltyshev af00e3e191 Add username to user 2020-04-03 00:35:25 +05:00
Maksim Eltyshev ec222a5e32 Update readme 2020-03-31 14:03:50 +05:00
Maksim Eltyshev bfd21ec05a Add new label colors 2020-03-31 03:00:58 +05:00
Maksim Eltyshev d1b74f9d11 Update dependencies 2020-03-25 00:15:47 +05:00
Maksim Eltyshev 4da29f2d8e Update dependencies 2020-03-14 20:58:48 +05:00
Maksim Eltyshev de7a8cd8d4 Downgrade bcrypt for Alpine Linux compatibility 2020-02-25 18:42:00 +05:00
Maksim Eltyshev b93c5aa070 Update dependencies 2020-02-25 02:10:27 +05:00
Maksim Eltyshev 5083e7ad57 Rename user table, remove ES6 import. Closes #6, closes #7 2020-02-25 01:33:54 +05:00
Maksim Eltyshev 9c7c96a780 Move from prettier-eslint to eslint-plugin-prettier, update dependencies 2020-02-03 18:42:31 +05:00
Maksim Eltyshev f0e7fb8fd1 Update dependencies 2020-01-14 13:10:34 +05:00
Maksim Eltyshev dc0f71624b Update dependencies 2019-12-30 04:24:06 +05:00
Maksim Eltyshev 622f56c77c Update Alpine repository branch in Dockerfile. Closes #4 2019-12-12 13:05:55 +05:00
Maksim Eltyshev 8b1ca9e52b Update dependencies 2019-12-11 04:23:33 +05:00
Maksim Eltyshev b4fd32fd17 Update dependencies 2019-11-18 11:59:37 +05:00
Maksim Eltyshev 00c7f514ae Add visibility toggle of password input. Closes #2 2019-11-15 03:45:59 +05:00
Maksim Eltyshev a11f6260c0 Code formatting with prettier, change eslint config for the server 2019-11-05 18:01:42 +05:00
Maksim Eltyshev b7f37f0f96 Fix handler in form/field hooks 2019-10-31 23:07:41 +05:00
Maksim Eltyshev 27939cbd5e Update dependencies 2019-10-30 21:21:01 +05:00
Maksim Eltyshev 4a5980cd82 Update dependencies 2019-10-29 14:19:51 +05:00
Maksim Eltyshev c6bbd038e2 Update dependencies 2019-10-28 23:23:36 +05:00
Maksim Eltyshev 3896e0688e Update dependencies 2019-10-26 03:51:36 +05:00
Maksim Eltyshev 859c09d05e Fix api transformers, intercept by error message instead of undefined 2019-10-25 21:38:14 +05:00
Maksim Eltyshev 17d2781d33 Update dependencies 2019-10-24 23:45:22 +05:00
Maksim Eltyshev f96c3e83de Update dependencies 2019-10-23 23:01:06 +05:00
Maksim Eltyshev 671bfc5efa Update dependencies 2019-10-22 19:46:11 +05:00
Maksim Eltyshev 0e9ae2a715 Remove axios from dependencies 2019-10-21 16:57:15 +05:00
Maksim Eltyshev 97856411a9 Update dependencies 2019-10-20 23:35:09 +05:00
Maksim Eltyshev e07b0bc5b1 Fix timer counting, fix flickering on comment creation, update dependencies 2019-10-19 17:11:37 +05:00
Maksim Eltyshev 2566ff376e Add email and password change functionality for a current user, remove deep compare hooks 2019-10-18 08:06:34 +05:00
Maksim Eltyshev e564729598 Fix typo in en-US translation. Closes #1 2019-10-17 18:28:15 +05:00
Maksim Eltyshev 1f38c6073c Small UI improvements, update dependencies 2019-10-16 22:48:24 +05:00
Maksim Eltyshev d770470371 Update gitignore 2019-10-15 20:36:18 +05:00
Maksim Eltyshev 5b9cad42fd Update server scaffold 2019-10-14 21:50:52 +05:00
Maksim Eltyshev dea090051c Update dependencies 2019-10-13 07:50:45 +05:00
Maksim Eltyshev 1d047c8e55 Update favicon and manifest files 2019-10-11 19:50:31 +05:00
Maksim Eltyshev dab8159864 Update readme 2019-10-11 16:28:23 +05:00
Maksim Eltyshev e8139b29d5 Change id generation algorithm, display notifications total on the main page 2019-10-10 14:51:59 +05:00
Maksim Eltyshev 4911816734 Allow creating label without name 2019-10-09 18:48:19 +05:00
Maksim Eltyshev b9d4024957 Fix main page scrollbar and socket status position 2019-10-09 01:05:06 +05:00
Maksim Eltyshev dc09246d8a Update dependencies 2019-10-07 21:59:29 +05:00
Maksim Eltyshev 3b050fe5b8 Update readme 2019-10-05 20:46:34 +05:00
Maksim Eltyshev a09a53ed12 Add code of conduct 2019-10-05 19:44:16 +05:00
Maksim Eltyshev 51013b6a32 Rename deadline to due date, update dependencies 2019-10-05 06:12:36 +05:00
Maksim Eltyshev 64acf0ac56 Add secret key to environment variables, update readme 2019-10-04 04:19:46 +05:00
Maksim Eltyshev 0eef38da66 Set popup max-height, update public files, update dependencies 2019-10-03 20:07:41 +05:00
Maksim Eltyshev ded98d412b Update readme 2019-10-03 17:51:11 +05:00
Maksim Eltyshev 18f0cc59dd Markdown support in card description and comment, fixes for mobile devices, update dependencies 2019-10-03 03:02:46 +05:00
Maksim Eltyshev 530e4c26ee Fix client lint script, update dependencies 2019-10-01 18:33:52 +05:00
Maksim Eltyshev 0ff5a23b6d Fix repository language, update readme 2019-10-01 04:58:20 +05:00
Maksim Eltyshev d24c9a7968 Docker, update readme, update dependencies 2019-10-01 04:18:33 +05:00
Maksim Eltyshev 6eb431f721 Mute socket messages in production, fix max-len, update dependencies 2019-09-25 02:26:31 +05:00
Maksim Eltyshev 7bd12cd8b8 Update dependencies 2019-09-09 01:40:06 +05:00
Maksim Eltyshev 562d855d46 Add client demo 2019-09-04 22:25:24 +05:00
Maksim Eltyshev 517a508cce Fix security vulnerabilities, update dependencies 2019-09-03 01:16:09 +05:00
Maksim Eltyshev c7ffc0652a Fix dotenv path in knexfile 2019-08-31 04:43:35 +05:00
Maksim Eltyshev 5ffef61fe7 Initial commit 2019-08-31 04:07:25 +05:00
830 changed files with 135718 additions and 3337 deletions
+10
View File
@@ -0,0 +1,10 @@
*/node_modules
*/.gitignore
*/README.md
**/.DS_Store
**/.gitkeep
server/public/*
server/views/*
client/build
+1
View File
@@ -0,0 +1 @@
client/src/lib/custom-ui/styles.css linguist-vendored
+5
View File
@@ -0,0 +1,5 @@
node_modules
docker-compose.override.yml
.vscode
.DS_Store
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@plankanban.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+49
View File
@@ -0,0 +1,49 @@
# Contributing to Planka
First off, thanks for taking the time to contribute!
## Code of Conduct
This project and everyone participating in it is governed by the [Planka Code of Conduct](https://github.com/plankanban/planka/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
## How Can I Contribute?
### Reporting Bugs
Feel free to create a bug report as a new issue on GitHub. Before creating, please check if there is already existing one. When creating a bug report, please include as many details as possible.
### Suggesting Enhancements
Feel free to create an enhancement suggestion as a new issue on GitHub. Before creating, please check if there is already existing one. When creating an enhancement suggestion, please include as many details as possible.
### Pull Requests
Before submitting a pull request please discuss with the core team by creating or commenting in an issue on GitHub – we'd also love to hear from you in the discussions. This way we can ensure that an approach is agreed on before code is written. This will result in a much higher liklihood of your code being accepted.
If you’re looking for ways to get started, here's a list of ways to help us improve Planka:
- [Translation](https://github.com/plankanban/planka/issues/66) into other languages
- Issues with [`good first issue`](https://github.com/plankanban/planka/labels/good%20first%20issue) label
- Performance improvements, both on client and server
- Developer happiness and documentation
- Bugs and other issues listed on GitHub
## Styleguides
### Git Commit Messages
Commit messages should follow the [commit message convention](https://conventionalcommits.org), so changelogs could be generated automatically by that.
Additional rules:
- Separate subject from body with a blank line
- Limit the subject line to 70 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Use the body to explain what and why vs. how
- Each commit should be a single, stable change
### JavaScript
All JavaScript code should follow this [JavaScript style guide](https://github.com/airbnb/javascript). The pre-commit hook will help you find linting errors before committing.
+64
View File
@@ -0,0 +1,64 @@
FROM node:lts AS client-builder
WORKDIR /app
COPY client/package.json client/package-lock.json ./
RUN npm install npm@latest --global \
&& npm install
COPY client .
RUN npm run build
FROM node:lts-alpine
WORKDIR /app
COPY server/.npmrc server/package.json server/package-lock.json ./
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 \
&& cd $OLDPWD \
&& rm -rf /tmp/vips-${VIPS_VERSION} \
&& npm install npm@latest --global \
&& npm install --production \
&& apk del vips-dependencies --purge
COPY docker-start.sh start.sh
COPY server .
RUN chmod +x start.sh \
&& cp .env.sample .env
COPY --from=client-builder /app/build public
COPY --from=client-builder /app/build/index.html views
VOLUME /app/public/user-avatars
VOLUME /app/public/project-background-images
VOLUME /app/private/attachments
EXPOSE 1337
CMD ["./start.sh"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Maksim Eltyshev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+86
View File
@@ -0,0 +1,86 @@
# Planka
![David (path)](https://img.shields.io/github/package-json/v/plankanban/planka) ![Docker Pulls](https://img.shields.io/docker/pulls/meltyshev/planka) ![GitHub](https://img.shields.io/github/license/plankanban/planka)
A Trello-like kanban board built with React and Redux.
![](https://raw.githubusercontent.com/plankanban/planka/master/demo.gif)
[**Client demo**](https://plankanban.github.io/planka) (without server features).
## Features
- Create projects, boards, lists, cards, labels and tasks
- Add card members, track time, set a due date, add attachments, write comments
- Markdown support in a card description and comment
- Filter by members and labels
- Customize project background
- Real-time updates
- User notifications
- Internationalization
## Deploy
### Docker Compose
[![](https://d207aa93qlcgug.cloudfront.net/1.95.5.qa/img/nav/docker-logo-loggedout.png)](https://hub.docker.com/r/meltyshev/planka)
- Make sure you have [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed and operational.
- Create `docker-compose.yml` based on [the example](https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml). This is the ONLY file you will need. You can create this file on your own machine by copy and pasting the content.
- Edit `BASE_URL` to match your domain name or IP address.
- Edit `SECRET_KEY` with random value. You can generate it by `openssl rand -hex 64`.
Download the docker-compose.yml:
```
curl -L https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml -o docker-compose.yml
```
Pull images and start services:
```
docker-compose up -d
```
Demo user: demo@demo.demo demo
## Development
Clone the repository and install dependencies:
```
git clone https://github.com/plankanban/planka.git
cd planka
npm install
```
Either use a local database or start the provided development database:
```
docker-compose -f docker-compose-dev.yml up
```
Create `server/.env` based on `server/.env.sample` and edit `DATABASE_URL` if needed, then initialize the database:
```
npm run server:db:init
```
Start the development server:
```
npm start
```
Demo user: demo@demo.demo demo
## Tech stack
- React, Redux, Redux-Saga, Redux-ORM, Semantic UI React, react-beautiful-dnd
- Sails.js, Knex.js
- PostgreSQL
## License
Planka is [MIT licensed](https://github.com/plankanban/planka/blob/master/LICENSE).
-80
View File
@@ -1,80 +0,0 @@
{
"files": {
"main.css": "/planka/static/css/main.859cd79e.css",
"main.js": "/planka/static/js/main.5081d4cc.js",
"static/js/801.9773e58e.chunk.js": "/planka/static/js/801.9773e58e.chunk.js",
"static/js/467.d02e66c6.chunk.js": "/planka/static/js/467.d02e66c6.chunk.js",
"static/js/514.592fb053.chunk.js": "/planka/static/js/514.592fb053.chunk.js",
"static/js/294.e0243dff.chunk.js": "/planka/static/js/294.e0243dff.chunk.js",
"static/js/394.5692e060.chunk.js": "/planka/static/js/394.5692e060.chunk.js",
"static/js/117.2c68642f.chunk.js": "/planka/static/js/117.2c68642f.chunk.js",
"static/js/181.24dbd8ba.chunk.js": "/planka/static/js/181.24dbd8ba.chunk.js",
"static/js/902.1af73487.chunk.js": "/planka/static/js/902.1af73487.chunk.js",
"static/js/875.4756cbe5.chunk.js": "/planka/static/js/875.4756cbe5.chunk.js",
"static/js/694.e5cf50a6.chunk.js": "/planka/static/js/694.e5cf50a6.chunk.js",
"static/js/632.9315886b.chunk.js": "/planka/static/js/632.9315886b.chunk.js",
"static/js/812.77d21c8d.chunk.js": "/planka/static/js/812.77d21c8d.chunk.js",
"static/js/215.bb7d98af.chunk.js": "/planka/static/js/215.bb7d98af.chunk.js",
"static/js/683.e8a7c172.chunk.js": "/planka/static/js/683.e8a7c172.chunk.js",
"static/media/big-picture.jpg": "/planka/static/media/big-picture.b78130bf7100f3416c1c.jpg",
"static/media/cover.jpg": "/planka/static/media/cover.a3c21cab987867165c62.jpg",
"static/media/simple-picture.jpg": "/planka/static/media/simple-picture.0442003259f074c6cc5d.jpg",
"static/media/brand-icons.svg": "/planka/static/media/brand-icons.6729d29753e000c17489.svg",
"static/media/simple-pdf.pdf": "/planka/static/media/simple-pdf.2b466d228796d7c86d15.pdf",
"static/media/fontawesome-webfont.svg?v=4.7.0": "/planka/static/media/fontawesome-webfont.c1e38fd9e0e74ba58f7a.svg",
"static/media/icons.svg": "/planka/static/media/icons.62d9dae4e0040e81c980.svg",
"static/media/original.jpg": "/planka/static/media/original.fd3fb2a1d11429b0ec28.jpg",
"static/media/high-picture.jpg": "/planka/static/media/high-picture.28d94c085a2a9ac5ccf3.jpg",
"static/media/fontawesome-webfont.eot": "/planka/static/media/fontawesome-webfont.8b43027f47b20503057d.eot",
"static/media/fontawesome-webfont.ttf?v=4.7.0": "/planka/static/media/fontawesome-webfont.1e59d2330b4c6deb84b3.ttf",
"static/media/outline-icons.svg": "/planka/static/media/outline-icons.9c4845b4b41ef40a22fa.svg",
"static/media/icons.eot": "/planka/static/media/icons.a01e3f2d6c83dc3aee17.eot",
"static/media/icons.ttf": "/planka/static/media/icons.c656b8caa454ed19b9a2.ttf",
"static/media/cover-256.jpg": "/planka/static/media/cover-256.4acd3319c6762c5d1b4b.jpg",
"static/media/brand-icons.eot": "/planka/static/media/brand-icons.d68fa3e67dbb653a13ce.eot",
"static/media/brand-icons.ttf": "/planka/static/media/brand-icons.65a2fb6d9aaa164b41a0.ttf",
"static/media/fontawesome-webfont.woff?v=4.7.0": "/planka/static/media/fontawesome-webfont.f691f37e57f04c152e23.woff",
"static/media/cover-336.jpg": "/planka/static/media/cover-336.d1a186776f06837a4ec3.jpg",
"static/media/fontawesome-webfont.woff2?v=4.7.0": "/planka/static/media/fontawesome-webfont.20fd1704ea223900efa9.woff2",
"static/media/brand-icons.woff": "/planka/static/media/brand-icons.cac87dc00c87a5d74711.woff",
"static/media/brand-icons.woff2": "/planka/static/media/brand-icons.278156e41e0ad908cf7f.woff2",
"static/media/marked.cjs": "/planka/static/media/marked.2244abd198e53bc0b3d6.cjs",
"static/media/icons.woff": "/planka/static/media/icons.425399f81e4ce7cbd967.woff",
"static/media/Nunitoga-Medium.woff2": "/planka/static/media/Nunitoga-Medium.bae369ea6c0c9ad31db6.woff2",
"static/media/Nunitoga-BoldItalic.woff2": "/planka/static/media/Nunitoga-BoldItalic.06ec745f47d8c9431b42.woff2",
"static/media/Nunitoga-MediumItalic.woff2": "/planka/static/media/Nunitoga-MediumItalic.5ce0915188332f04e46d.woff2",
"static/media/Nunitoga-LightItalic.woff2": "/planka/static/media/Nunitoga-LightItalic.f1b251d4faa9826b3ea2.woff2",
"static/media/Nunitoga-Bold.woff2": "/planka/static/media/Nunitoga-Bold.46fd9a43c89bf2b6a1fe.woff2",
"static/media/Nunitoga-Light.woff2": "/planka/static/media/Nunitoga-Light.e9d89c979b64722f34ec.woff2",
"static/media/icons.woff2": "/planka/static/media/icons.38c6d8bab26db77d8c80.woff2",
"static/media/outline-icons.eot": "/planka/static/media/outline-icons.752905fa5edf21fc52a1.eot",
"static/media/outline-icons.ttf": "/planka/static/media/outline-icons.5367103510b27b784827.ttf",
"static/media/flags.png": "/planka/static/media/flags.99f63ae7a743f21ab308.png",
"static/media/logo.png": "/planka/static/media/logo.f8c2b962d0f2bd7f46cb.png",
"static/media/outline-icons.woff": "/planka/static/media/outline-icons.ddae9b1ba9b0b42f5880.woff",
"static/media/outline-icons.woff2": "/planka/static/media/outline-icons.687a4990ea22bb1a49d4.woff2",
"index.html": "/planka/index.html",
"static/media/plus-icon.svg": "/planka/static/media/plus-icon.e46c682424b8c98c4f7bd0f367a260c2.svg",
"static/media/plus-math-icon.svg": "/planka/static/media/plus-math-icon.cfa58840e75860dcf79a0b21a1cca781.svg",
"main.859cd79e.css.map": "/planka/static/css/main.859cd79e.css.map",
"main.5081d4cc.js.map": "/planka/static/js/main.5081d4cc.js.map",
"801.9773e58e.chunk.js.map": "/planka/static/js/801.9773e58e.chunk.js.map",
"467.d02e66c6.chunk.js.map": "/planka/static/js/467.d02e66c6.chunk.js.map",
"514.592fb053.chunk.js.map": "/planka/static/js/514.592fb053.chunk.js.map",
"294.e0243dff.chunk.js.map": "/planka/static/js/294.e0243dff.chunk.js.map",
"394.5692e060.chunk.js.map": "/planka/static/js/394.5692e060.chunk.js.map",
"117.2c68642f.chunk.js.map": "/planka/static/js/117.2c68642f.chunk.js.map",
"181.24dbd8ba.chunk.js.map": "/planka/static/js/181.24dbd8ba.chunk.js.map",
"902.1af73487.chunk.js.map": "/planka/static/js/902.1af73487.chunk.js.map",
"875.4756cbe5.chunk.js.map": "/planka/static/js/875.4756cbe5.chunk.js.map",
"694.e5cf50a6.chunk.js.map": "/planka/static/js/694.e5cf50a6.chunk.js.map",
"632.9315886b.chunk.js.map": "/planka/static/js/632.9315886b.chunk.js.map",
"812.77d21c8d.chunk.js.map": "/planka/static/js/812.77d21c8d.chunk.js.map",
"215.bb7d98af.chunk.js.map": "/planka/static/js/215.bb7d98af.chunk.js.map",
"683.e8a7c172.chunk.js.map": "/planka/static/js/683.e8a7c172.chunk.js.map"
},
"entrypoints": [
"static/css/main.859cd79e.css",
"static/js/main.5081d4cc.js"
]
}
+23
View File
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+1
View File
@@ -0,0 +1 @@
# Planka client
+40166
View File
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
{
"name": "planka-client",
"private": true,
"scripts": {
"build": "react-scripts build",
"eject": "react-scripts eject",
"lint": "eslint --ext js,jsx src",
"start": "react-scripts start",
"test": "react-scripts test"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"eslintConfig": {
"env": {
"browser": true,
"jest": true
},
"parser": "@babel/eslint-parser",
"parserOptions": {
"babelOptions": {
"presets": [
"airbnb"
]
},
"requireConfigFile": false
},
"plugins": [
"prettier"
],
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:prettier/recommended"
],
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"src/setupTests.js",
"**/*.test.js"
]
}
],
"prettier/prettier": [
"error",
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
]
}
},
"dependencies": {
"classnames": "^2.2.6",
"connected-react-router": "^6.9.2",
"date-fns": "^2.28.0",
"dequal": "^2.0.2",
"history": "^4.10.1",
"i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.4",
"initials": "^3.1.2",
"js-cookie": "^3.0.1",
"lodash": "^4.17.20",
"node-sass": "^7.0.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.0.0",
"react-datepicker": "^4.7.0",
"react-dom": "^17.0.2",
"react-dropzone": "^12.0.5",
"react-i18next": "^11.16.6",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.2",
"react-redux": "^7.2.8",
"react-router-dom": "^5.3.1",
"react-scripts": "5.0.1",
"react-textarea-autosize": "^8.3.3",
"redux": "^4.1.2",
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.1.3",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.5",
"sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.2",
"socket.io-client": "^2.3.1",
"validator": "^13.7.0",
"whatwg-fetch": "^3.5.0"
},
"devDependencies": {
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
"babel-preset-airbnb": "^5.0.0",
"chai": "^4.3.6",
"enzyme": "^3.11.0",
"eslint": "^8.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"jest-enzyme": "^7.1.2",
"prettier": "2.6.2",
"react-test-renderer": "^17.0.2"
}
}

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Planka is an open source project management software"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Planka</title>
</head>
<body id="app">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

+22
View File
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,26 @@
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,
},
});
+90
View File
@@ -0,0 +1,90 @@
import ActionTypes from '../constants/ActionTypes';
export const createAttachment = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE,
payload: {
attachment,
},
});
createAttachment.success = (localId, attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE__SUCCESS,
payload: {
localId,
attachment,
},
});
createAttachment.failure = (localId, error) => ({
type: ActionTypes.ATTACHMENT_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleAttachmentCreate = (attachment) => ({
type: ActionTypes.ATTACHMENT_CREATE_HANDLE,
payload: {
attachment,
},
});
export const updateAttachment = (id, data) => ({
type: ActionTypes.ATTACHMENT_UPDATE,
payload: {
id,
data,
},
});
updateAttachment.success = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE__SUCCESS,
payload: {
attachment,
},
});
updateAttachment.failure = (id, error) => ({
type: ActionTypes.ATTACHMENT_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleAttachmentUpdate = (attachment) => ({
type: ActionTypes.ATTACHMENT_UPDATE_HANDLE,
payload: {
attachment,
},
});
export const deleteAttachment = (id) => ({
type: ActionTypes.ATTACHMENT_DELETE,
payload: {
id,
},
});
deleteAttachment.success = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE__SUCCESS,
payload: {
attachment,
},
});
deleteAttachment.failure = (id, error) => ({
type: ActionTypes.ATTACHMENT_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleAttachmentDelete = (attachment) => ({
type: ActionTypes.ATTACHMENT_DELETE_HANDLE,
payload: {
attachment,
},
});
+97
View File
@@ -0,0 +1,97 @@
import ActionTypes from '../constants/ActionTypes';
export const createBoardMembership = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE,
payload: {
boardMembership,
},
});
createBoardMembership.success = (localId, boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE__SUCCESS,
payload: {
localId,
boardMembership,
},
});
createBoardMembership.failure = (localId, error) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleBoardMembershipCreate = (
boardMembership,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
payload: {
boardMembership,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
},
});
export const deleteBoardMembership = (id) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
payload: {
id,
},
});
deleteBoardMembership.success = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS,
payload: {
boardMembership,
},
});
deleteBoardMembership.failure = (id, error) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleBoardMembershipDelete = (boardMembership) => ({
type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
payload: {
boardMembership,
},
});
+135
View File
@@ -0,0 +1,135 @@
import ActionTypes from '../constants/ActionTypes';
export const createBoard = (board) => ({
type: ActionTypes.BOARD_CREATE,
payload: {
board,
},
});
createBoard.success = (localId, board, boardMemberships) => ({
type: ActionTypes.BOARD_CREATE__SUCCESS,
payload: {
localId,
board,
boardMemberships,
},
});
createBoard.failure = (localId, error) => ({
type: ActionTypes.BOARD_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleBoardCreate = (board) => ({
type: ActionTypes.BOARD_CREATE_HANDLE,
payload: {
board,
},
});
export const fetchBoard = (id) => ({
type: ActionTypes.BOARD_FETCH,
payload: {
id,
},
});
fetchBoard.success = (
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.BOARD_FETCH__SUCCESS,
payload: {
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
fetchBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_FETCH__FAILURE,
payload: {
id,
error,
},
});
export const updateBoard = (id, data) => ({
type: ActionTypes.BOARD_UPDATE,
payload: {
id,
data,
},
});
updateBoard.success = (board) => ({
type: ActionTypes.BOARD_UPDATE__SUCCESS,
payload: {
board,
},
});
updateBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleBoardUpdate = (board) => ({
type: ActionTypes.BOARD_UPDATE_HANDLE,
payload: {
board,
},
});
export const deleteBoard = (id) => ({
type: ActionTypes.BOARD_DELETE,
payload: {
id,
},
});
deleteBoard.success = (board) => ({
type: ActionTypes.BOARD_DELETE__SUCCESS,
payload: {
board,
},
});
deleteBoard.failure = (id, error) => ({
type: ActionTypes.BOARD_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleBoardDelete = (board) => ({
type: ActionTypes.BOARD_DELETE_HANDLE,
payload: {
board,
},
});
+90
View File
@@ -0,0 +1,90 @@
import ActionTypes from '../constants/ActionTypes';
export const createCard = (card) => ({
type: ActionTypes.CARD_CREATE,
payload: {
card,
},
});
createCard.success = (localId, card) => ({
type: ActionTypes.CARD_CREATE__SUCCESS,
payload: {
localId,
card,
},
});
createCard.failure = (localId, error) => ({
type: ActionTypes.CARD_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleCardCreate = (card) => ({
type: ActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
},
});
export const updateCard = (id, data) => ({
type: ActionTypes.CARD_UPDATE,
payload: {
id,
data,
},
});
updateCard.success = (card) => ({
type: ActionTypes.CARD_UPDATE__SUCCESS,
payload: {
card,
},
});
updateCard.failure = (id, error) => ({
type: ActionTypes.CARD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleCardUpdate = (card) => ({
type: ActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
},
});
export const deleteCard = (id) => ({
type: ActionTypes.CARD_DELETE,
payload: {
id,
},
});
deleteCard.success = (card) => ({
type: ActionTypes.CARD_DELETE__SUCCESS,
payload: {
card,
},
});
deleteCard.failure = (id, error) => ({
type: ActionTypes.CARD_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleCardDelete = (card) => ({
type: ActionTypes.CARD_DELETE_HANDLE,
payload: {
card,
},
});
+69
View File
@@ -0,0 +1,69 @@
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,
},
});
+41
View File
@@ -0,0 +1,41 @@
import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const initializeCore = (
user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
) => ({
type: ActionTypes.CORE_INITIALIZE,
payload: {
user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
},
});
+22
View File
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,7 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const fetchActionsInCurrentCard = () => ({
type: EntryActionTypes.ACTIONS_IN_CURRENT_CARD_FETCH,
payload: {},
});
+45
View File
@@ -0,0 +1,45 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createAttachmentInCurrentCard = (data) => ({
type: EntryActionTypes.ATTACHMENT_IN_CURRENT_CARD_CREATE,
payload: {
data,
},
});
export const handleAttachmentCreate = (attachment, requestId) => ({
type: EntryActionTypes.ATTACHMENT_CREATE_HANDLE,
payload: {
attachment,
requestId,
},
});
export const updateAttachment = (id, data) => ({
type: EntryActionTypes.ATTACHMENT_UPDATE,
payload: {
id,
data,
},
});
export const handleAttachmentUpdate = (attachment) => ({
type: EntryActionTypes.ATTACHMENT_UPDATE_HANDLE,
payload: {
attachment,
},
});
export const deleteAttachment = (id) => ({
type: EntryActionTypes.ATTACHMENT_DELETE,
payload: {
id,
},
});
export const handleAttachmentDelete = (attachment) => ({
type: EntryActionTypes.ATTACHMENT_DELETE_HANDLE,
payload: {
attachment,
},
});
@@ -0,0 +1,29 @@
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,
},
});
+59
View File
@@ -0,0 +1,59 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createBoardInCurrentProject = (data) => ({
type: EntryActionTypes.BOARD_IN_CURRENT_PROJECT_CREATE,
payload: {
data,
},
});
export const handleBoardCreate = (board) => ({
type: EntryActionTypes.BOARD_CREATE_HANDLE,
payload: {
board,
},
});
export const fetchBoard = (id) => ({
type: EntryActionTypes.BOARD_FETCH,
payload: {
id,
},
});
export const updateBoard = (id, data) => ({
type: EntryActionTypes.BOARD_UPDATE,
payload: {
id,
data,
},
});
export const handleBoardUpdate = (board) => ({
type: EntryActionTypes.BOARD_UPDATE_HANDLE,
payload: {
board,
},
});
export const moveBoard = (id, index) => ({
type: EntryActionTypes.BOARD_MOVE,
payload: {
id,
index,
},
});
export const deleteBoard = (id) => ({
type: EntryActionTypes.BOARD_DELETE,
payload: {
id,
},
});
export const handleBoardDelete = (board) => ({
type: EntryActionTypes.BOARD_DELETE_HANDLE,
payload: {
board,
},
});
+93
View File
@@ -0,0 +1,93 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createCard = (listId, data) => ({
type: EntryActionTypes.CARD_CREATE,
payload: {
listId,
data,
},
});
export const handleCardCreate = (card) => ({
type: EntryActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
},
});
export const updateCard = (id, data) => ({
type: EntryActionTypes.CARD_UPDATE,
payload: {
id,
data,
},
});
export const updateCurrentCard = (data) => ({
type: EntryActionTypes.CURRENT_CARD_UPDATE,
payload: {
data,
},
});
export const handleCardUpdate = (card) => ({
type: EntryActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
},
});
export const moveCard = (id, listId, index = 0) => ({
type: EntryActionTypes.CARD_MOVE,
payload: {
id,
listId,
index,
},
});
export const moveCurrentCard = (listId, index = 0) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: {
listId,
index,
},
});
export const transferCard = (id, boardId, listId, index = 0) => ({
type: EntryActionTypes.CARD_TRANSFER,
payload: {
id,
boardId,
listId,
index,
},
});
export const transferCurrentCard = (boardId, listId, index = 0) => ({
type: EntryActionTypes.CURRENT_CARD_TRANSFER,
payload: {
boardId,
listId,
index,
},
});
export const deleteCard = (id) => ({
type: EntryActionTypes.CARD_DELETE,
payload: {
id,
},
});
export const deleteCurrentCard = () => ({
type: EntryActionTypes.CURRENT_CARD_DELETE,
payload: {},
});
export const handleCardDelete = (card) => ({
type: EntryActionTypes.CARD_DELETE_HANDLE,
payload: {
card,
},
});
+23
View File
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,7 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const initializeCore = () => ({
type: EntryActionTypes.CORE_INITIALIZE,
payload: {},
});
+18
View File
@@ -0,0 +1,18 @@
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';
+102
View File
@@ -0,0 +1,102 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createLabelInCurrentBoard = (data) => ({
type: EntryActionTypes.LABEL_IN_CURRENT_BOARD_CREATE,
payload: {
data,
},
});
export const handleLabelCreate = (label) => ({
type: EntryActionTypes.LABEL_CREATE_HANDLE,
payload: {
label,
},
});
export const updateLabel = (id, data) => ({
type: EntryActionTypes.LABEL_UPDATE,
payload: {
id,
data,
},
});
export const handleLabelUpdate = (label) => ({
type: EntryActionTypes.LABEL_UPDATE_HANDLE,
payload: {
label,
},
});
export const deleteLabel = (id) => ({
type: EntryActionTypes.LABEL_DELETE,
payload: {
id,
},
});
export const handleLabelDelete = (label) => ({
type: EntryActionTypes.LABEL_DELETE_HANDLE,
payload: {
label,
},
});
export const addLabelToCard = (id, cardId) => ({
type: EntryActionTypes.LABEL_TO_CARD_ADD,
payload: {
id,
cardId,
},
});
export const addLabelToCurrentCard = (id) => ({
type: EntryActionTypes.LABEL_TO_CURRENT_CARD_ADD,
payload: {
id,
},
});
export const handleLabelToCardAdd = (cardLabel) => ({
type: EntryActionTypes.LABEL_TO_CARD_ADD_HANDLE,
payload: {
cardLabel,
},
});
export const removeLabelFromCard = (id, cardId) => ({
type: EntryActionTypes.LABEL_FROM_CARD_REMOVE,
payload: {
id,
cardId,
},
});
export const removeLabelFromCurrentCard = (id) => ({
type: EntryActionTypes.LABEL_FROM_CURRENT_CARD_REMOVE,
payload: {
id,
},
});
export const handleLabelFromCardRemove = (cardLabel) => ({
type: EntryActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,
payload: {
cardLabel,
},
});
export const addLabelToFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.LABEL_TO_FILTER_IN_CURRENT_BOARD_ADD,
payload: {
id,
},
});
export const removeLabelFromFilterInCurrentBoard = (id) => ({
type: EntryActionTypes.LABEL_FROM_FILTER_IN_CURRENT_BOARD_REMOVE,
payload: {
id,
},
});
+52
View File
@@ -0,0 +1,52 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createListInCurrentBoard = (data) => ({
type: EntryActionTypes.LIST_IN_CURRENT_BOARD_CREATE,
payload: {
data,
},
});
export const handleListCreate = (list) => ({
type: EntryActionTypes.LIST_CREATE_HANDLE,
payload: {
list,
},
});
export const updateList = (id, data) => ({
type: EntryActionTypes.LIST_UPDATE,
payload: {
id,
data,
},
});
export const handleListUpdate = (list) => ({
type: EntryActionTypes.LIST_UPDATE_HANDLE,
payload: {
list,
},
});
export const moveList = (id, index) => ({
type: EntryActionTypes.LIST_MOVE,
payload: {
id,
index,
},
});
export const deleteList = (id) => ({
type: EntryActionTypes.LIST_DELETE,
payload: {
id,
},
});
export const handleListDelete = (list) => ({
type: EntryActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
},
});
+18
View File
@@ -0,0 +1,18 @@
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: {},
});
+35
View File
@@ -0,0 +1,35 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
import ModalTypes from '../../constants/ModalTypes';
export const openUsersModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
type: ModalTypes.USERS,
},
});
export const openUserSettingsModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
type: ModalTypes.USER_SETTINGS,
},
});
export const openProjectAddModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
type: ModalTypes.PROJECT_ADD,
},
});
export const openProjectSettingsModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
type: ModalTypes.PROJECT_SETTINGS,
},
});
export const closeModal = () => ({
type: EntryActionTypes.MODAL_CLOSE,
payload: {},
});
+22
View File
@@ -0,0 +1,22 @@
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,
},
});
+29
View File
@@ -0,0 +1,29 @@
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,
},
});
+48
View File
@@ -0,0 +1,48 @@
import EntryActionTypes from '../../constants/EntryActionTypes';
export const createProject = (data) => ({
type: EntryActionTypes.PROJECT_CREATE,
payload: {
data,
},
});
export const handleProjectCreate = (project) => ({
type: EntryActionTypes.PROJECT_CREATE_HANDLE,
payload: {
project,
},
});
export const updateCurrentProject = (data) => ({
type: EntryActionTypes.CURRENT_PROJECT_UPDATE,
payload: {
data,
},
});
export const handleProjectUpdate = (project) => ({
type: EntryActionTypes.PROJECT_UPDATE_HANDLE,
payload: {
project,
},
});
export const updateCurrentProjectBackgroundImage = (data) => ({
type: EntryActionTypes.CURRENT_PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: {
data,
},
});
export const deleteCurrentProject = () => ({
type: EntryActionTypes.CURRENT_PROJECT_DELETE,
payload: {},
});
export const handleProjectDelete = (project) => ({
type: EntryActionTypes.PROJECT_DELETE_HANDLE,
payload: {
project,
},
});
+11
View File
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,44 @@
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
@@ -0,0 +1,157 @@
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,
},
});
+19
View File
@@ -0,0 +1,19 @@
export * from './router';
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';
+168
View File
@@ -0,0 +1,168 @@
import ActionTypes from '../constants/ActionTypes';
export const createLabel = (label) => ({
type: ActionTypes.LABEL_CREATE,
payload: {
label,
},
});
createLabel.success = (localId, label) => ({
type: ActionTypes.LABEL_CREATE__SUCCESS,
payload: {
localId,
label,
},
});
createLabel.failure = (localId, error) => ({
type: ActionTypes.LABEL_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleLabelCreate = (label) => ({
type: ActionTypes.LABEL_CREATE_HANDLE,
payload: {
label,
},
});
export const updateLabel = (id, data) => ({
type: ActionTypes.LABEL_UPDATE,
payload: {
id,
data,
},
});
updateLabel.success = (label) => ({
type: ActionTypes.LABEL_UPDATE__SUCCESS,
payload: {
label,
},
});
updateLabel.failure = (id, error) => ({
type: ActionTypes.LABEL_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleLabelUpdate = (label) => ({
type: ActionTypes.LABEL_UPDATE_HANDLE,
payload: {
label,
},
});
export const deleteLabel = (id) => ({
type: ActionTypes.LABEL_DELETE,
payload: {
id,
},
});
deleteLabel.success = (label) => ({
type: ActionTypes.LABEL_DELETE__SUCCESS,
payload: {
label,
},
});
deleteLabel.failure = (id, error) => ({
type: ActionTypes.LABEL_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleLabelDelete = (label) => ({
type: ActionTypes.LABEL_DELETE_HANDLE,
payload: {
label,
},
});
export const addLabelToCard = (id, cardId) => ({
type: ActionTypes.LABEL_TO_CARD_ADD,
payload: {
id,
cardId,
},
});
addLabelToCard.success = (cardLabel) => ({
type: ActionTypes.LABEL_TO_CARD_ADD__SUCCESS,
payload: {
cardLabel,
},
});
addLabelToCard.failure = (id, cardId, error) => ({
type: ActionTypes.LABEL_TO_CARD_ADD__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleLabelToCardAdd = (cardLabel) => ({
type: ActionTypes.LABEL_TO_CARD_ADD_HANDLE,
payload: {
cardLabel,
},
});
export const removeLabelFromCard = (id, cardId) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE,
payload: {
id,
cardId,
},
});
removeLabelFromCard.success = (cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE__SUCCESS,
payload: {
cardLabel,
},
});
removeLabelFromCard.failure = (id, cardId, error) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleLabelFromCardRemove = (cardLabel) => ({
type: ActionTypes.LABEL_FROM_CARD_REMOVE_HANDLE,
payload: {
cardLabel,
},
});
export const addLabelToBoardFilter = (id, boardId) => ({
type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,
payload: {
id,
boardId,
},
});
export const removeLabelFromBoardFilter = (id, boardId) => ({
type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE,
payload: {
id,
boardId,
},
});
+90
View File
@@ -0,0 +1,90 @@
import ActionTypes from '../constants/ActionTypes';
export const createList = (list) => ({
type: ActionTypes.LIST_CREATE,
payload: {
list,
},
});
createList.success = (localId, list) => ({
type: ActionTypes.LIST_CREATE__SUCCESS,
payload: {
localId,
list,
},
});
createList.failure = (localId, error) => ({
type: ActionTypes.LIST_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleListCreate = (list) => ({
type: ActionTypes.LIST_CREATE_HANDLE,
payload: {
list,
},
});
export const updateList = (id, data) => ({
type: ActionTypes.LIST_UPDATE,
payload: {
id,
data,
},
});
updateList.success = (list) => ({
type: ActionTypes.LIST_UPDATE__SUCCESS,
payload: {
list,
},
});
updateList.failure = (id, error) => ({
type: ActionTypes.LIST_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleListUpdate = (list) => ({
type: ActionTypes.LIST_UPDATE_HANDLE,
payload: {
list,
},
});
export const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE,
payload: {
id,
},
});
deleteList.success = (list) => ({
type: ActionTypes.LIST_DELETE__SUCCESS,
payload: {
list,
},
});
deleteList.failure = (id, error) => ({
type: ActionTypes.LIST_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleListDelete = (list) => ({
type: ActionTypes.LIST_DELETE_HANDLE,
payload: {
list,
},
});
+32
View File
@@ -0,0 +1,32 @@
import ActionTypes from '../constants/ActionTypes';
export const authenticate = (data) => ({
type: ActionTypes.AUTHENTICATE,
payload: {
data,
},
});
authenticate.success = (accessToken) => ({
type: ActionTypes.AUTHENTICATE__SUCCESS,
payload: {
accessToken,
},
});
authenticate.failure = (error) => ({
type: ActionTypes.AUTHENTICATE__FAILURE,
payload: {
error,
},
});
export const clearAuthenticateError = () => ({
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
payload: {},
});
export const logout = () => ({
type: ActionTypes.LOGOUT,
payload: {},
});
+13
View File
@@ -0,0 +1,13 @@
import ActionTypes from '../constants/ActionTypes';
export const openModal = (type) => ({
type: ActionTypes.MODAL_OPEN,
payload: {
type,
},
});
export const closeModal = () => ({
type: ActionTypes.MODAL_CLOSE,
payload: {},
});
+40
View File
@@ -0,0 +1,40 @@
import ActionTypes from '../constants/ActionTypes';
export const handleNotificationCreate = (notification, users, cards, actions) => ({
type: ActionTypes.NOTIFICATION_CREATE_HANDLE,
payload: {
notification,
users,
cards,
actions,
},
});
export const deleteNotification = (id) => ({
type: ActionTypes.NOTIFICATION_DELETE,
payload: {
id,
},
});
deleteNotification.success = (notification) => ({
type: ActionTypes.NOTIFICATION_DELETE__SUCCESS,
payload: {
notification,
},
});
deleteNotification.failure = (id, error) => ({
type: ActionTypes.NOTIFICATION_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleNotificationDelete = (notification) => ({
type: ActionTypes.NOTIFICATION_DELETE_HANDLE,
payload: {
notification,
},
});
+101
View File
@@ -0,0 +1,101 @@
import ActionTypes from '../constants/ActionTypes';
export const createProjectManager = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE,
payload: {
projectManager,
},
});
createProjectManager.success = (localId, projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE__SUCCESS,
payload: {
localId,
projectManager,
},
});
createProjectManager.failure = (localId, error) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleProjectManagerCreate = (
projectManager,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
payload: {
projectManager,
project,
board,
users,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
handleProjectManagerCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE__PROJECT_FETCH,
payload: {
id,
currentUserId,
currentBoardId,
},
});
export const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE,
payload: {
id,
isCurrentUser,
isCurrentProject,
},
});
deleteProjectManager.success = (projectManager) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE__SUCCESS,
payload: {
projectManager,
},
});
deleteProjectManager.failure = (id, error) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({
type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
payload: {
projectManager,
isCurrentUser,
isCurrentProject,
},
});
+115
View File
@@ -0,0 +1,115 @@
import ActionTypes from '../constants/ActionTypes';
export const createProject = (data) => ({
type: ActionTypes.PROJECT_CREATE,
payload: {
data,
},
});
createProject.success = (project, projectManagers) => ({
type: ActionTypes.PROJECT_CREATE__SUCCESS,
payload: {
project,
projectManagers,
},
});
createProject.failure = (error) => ({
type: ActionTypes.PROJECT_CREATE__FAILURE,
payload: {
error,
},
});
export const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({
type: ActionTypes.PROJECT_CREATE_HANDLE,
payload: {
project,
users,
projectManagers,
boards,
boardMemberships,
},
});
export const updateProject = (id, data) => ({
type: ActionTypes.PROJECT_UPDATE,
payload: {
id,
data,
},
});
updateProject.success = (project) => ({
type: ActionTypes.PROJECT_UPDATE__SUCCESS,
payload: {
project,
},
});
updateProject.failure = (id, error) => ({
type: ActionTypes.PROJECT_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleProjectUpdate = (project) => ({
type: ActionTypes.PROJECT_UPDATE_HANDLE,
payload: {
project,
},
});
export const updateProjectBackgroundImage = (id) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE,
payload: {
id,
},
});
updateProjectBackgroundImage.success = (project) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__SUCCESS,
payload: {
project,
},
});
updateProjectBackgroundImage.failure = (id, error) => ({
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteProject = (id) => ({
type: ActionTypes.PROJECT_DELETE,
payload: {
id,
},
});
deleteProject.success = (project) => ({
type: ActionTypes.PROJECT_DELETE__SUCCESS,
payload: {
project,
},
});
deleteProject.failure = (id, error) => ({
type: ActionTypes.PROJECT_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleProjectDelete = (project) => ({
type: ActionTypes.PROJECT_DELETE_HANDLE,
payload: {
project,
},
});
+40
View File
@@ -0,0 +1,40 @@
import ActionTypes from '../constants/ActionTypes';
// eslint-disable-next-line import/prefer-default-export
export const handleLocationChange = (
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
notifications,
) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE,
payload: {
board,
users,
projects,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
notifications,
},
});
handleLocationChange.fetchBoard = (id) => ({
type: ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH,
payload: {
id,
},
});
+53
View File
@@ -0,0 +1,53 @@
import ActionTypes from '../constants/ActionTypes';
export const handleSocketDisconnect = () => ({
type: ActionTypes.SOCKET_DISCONNECT_HANDLE,
payload: {},
});
export const handleSocketReconnect = (
user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE,
payload: {
user,
board,
users,
projects,
projectManagers,
boards,
boardMemberships,
labels,
lists,
cards,
cardMemberships,
cardLabels,
tasks,
attachments,
actions,
notifications,
},
});
handleSocketReconnect.fetchCore = (currentUserId, currentBoardId) => ({
type: ActionTypes.SOCKET_RECONNECT_HANDLE__CORE_FETCH,
payload: {
currentUserId,
currentBoardId,
},
});
+90
View File
@@ -0,0 +1,90 @@
import ActionTypes from '../constants/ActionTypes';
export const createTask = (task) => ({
type: ActionTypes.TASK_CREATE,
payload: {
task,
},
});
createTask.success = (localId, task) => ({
type: ActionTypes.TASK_CREATE__SUCCESS,
payload: {
localId,
task,
},
});
createTask.failure = (localId, error) => ({
type: ActionTypes.TASK_CREATE__FAILURE,
payload: {
localId,
error,
},
});
export const handleTaskCreate = (task) => ({
type: ActionTypes.TASK_CREATE_HANDLE,
payload: {
task,
},
});
export const updateTask = (id, data) => ({
type: ActionTypes.TASK_UPDATE,
payload: {
id,
data,
},
});
updateTask.success = (task) => ({
type: ActionTypes.TASK_UPDATE__SUCCESS,
payload: {
task,
},
});
updateTask.failure = (id, error) => ({
type: ActionTypes.TASK_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleTaskUpdate = (task) => ({
type: ActionTypes.TASK_UPDATE_HANDLE,
payload: {
task,
},
});
export const deleteTask = (id) => ({
type: ActionTypes.TASK_DELETE,
payload: {
id,
},
});
deleteTask.success = (task) => ({
type: ActionTypes.TASK_DELETE__SUCCESS,
payload: {
task,
},
});
deleteTask.failure = (id, error) => ({
type: ActionTypes.TASK_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleTaskDelete = (task) => ({
type: ActionTypes.TASK_DELETE_HANDLE,
payload: {
task,
},
});
+286
View File
@@ -0,0 +1,286 @@
import ActionTypes from '../constants/ActionTypes';
export const createUser = (data) => ({
type: ActionTypes.USER_CREATE,
payload: {
data,
},
});
createUser.success = (user) => ({
type: ActionTypes.USER_CREATE__SUCCESS,
payload: {
user,
},
});
createUser.failure = (error) => ({
type: ActionTypes.USER_CREATE__FAILURE,
payload: {
error,
},
});
export const handleUserCreate = (user) => ({
type: ActionTypes.USER_CREATE_HANDLE,
payload: {
user,
},
});
export const clearUserCreateError = () => ({
type: ActionTypes.USER_CREATE_ERROR_CLEAR,
payload: {},
});
export const updateUser = (id, data) => ({
type: ActionTypes.USER_UPDATE,
payload: {
id,
data,
},
});
updateUser.success = (user) => ({
type: ActionTypes.USER_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUser.failure = (id, error) => ({
type: ActionTypes.USER_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const handleUserUpdate = (user, users, isCurrent) => ({
type: ActionTypes.USER_UPDATE_HANDLE,
payload: {
user,
users,
isCurrent,
},
});
export const updateUserEmail = (id, data) => ({
type: ActionTypes.USER_EMAIL_UPDATE,
payload: {
id,
data,
},
});
updateUserEmail.success = (user) => ({
type: ActionTypes.USER_EMAIL_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserEmail.failure = (id, error) => ({
type: ActionTypes.USER_EMAIL_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserEmailUpdateError = (id) => ({
type: ActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const updateUserPassword = (id, data) => ({
type: ActionTypes.USER_PASSWORD_UPDATE,
payload: {
id,
data,
},
});
updateUserPassword.success = (user) => ({
type: ActionTypes.USER_PASSWORD_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserPassword.failure = (id, error) => ({
type: ActionTypes.USER_PASSWORD_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserPasswordUpdateError = (id) => ({
type: ActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const updateUserUsername = (id, data) => ({
type: ActionTypes.USER_USERNAME_UPDATE,
payload: {
id,
data,
},
});
updateUserUsername.success = (user) => ({
type: ActionTypes.USER_USERNAME_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserUsername.failure = (id, error) => ({
type: ActionTypes.USER_USERNAME_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const clearUserUsernameUpdateError = (id) => ({
type: ActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR,
payload: {
id,
},
});
export const updateUserAvatar = (id) => ({
type: ActionTypes.USER_AVATAR_UPDATE,
payload: {
id,
},
});
updateUserAvatar.success = (user) => ({
type: ActionTypes.USER_AVATAR_UPDATE__SUCCESS,
payload: {
user,
},
});
updateUserAvatar.failure = (id, error) => ({
type: ActionTypes.USER_AVATAR_UPDATE__FAILURE,
payload: {
id,
error,
},
});
export const deleteUser = (id) => ({
type: ActionTypes.USER_DELETE,
payload: {
id,
},
});
deleteUser.success = (user) => ({
type: ActionTypes.USER_DELETE__SUCCESS,
payload: {
user,
},
});
deleteUser.failure = (id, error) => ({
type: ActionTypes.USER_DELETE__FAILURE,
payload: {
id,
error,
},
});
export const handleUserDelete = (user) => ({
type: ActionTypes.USER_DELETE_HANDLE,
payload: {
user,
},
});
export const addUserToCard = (id, cardId, isCurrent) => ({
type: ActionTypes.USER_TO_CARD_ADD,
payload: {
id,
cardId,
isCurrent,
},
});
addUserToCard.success = (cardMembership) => ({
type: ActionTypes.USER_TO_CARD_ADD__SUCCESS,
payload: {
cardMembership,
},
});
addUserToCard.failure = (id, cardId, error) => ({
type: ActionTypes.USER_TO_CARD_ADD__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleUserToCardAdd = (cardMembership) => ({
type: ActionTypes.USER_TO_CARD_ADD_HANDLE,
payload: {
cardMembership,
},
});
export const removeUserFromCard = (id, cardId) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE,
payload: {
id,
cardId,
},
});
removeUserFromCard.success = (cardMembership) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE__SUCCESS,
payload: {
cardMembership,
},
});
removeUserFromCard.failure = (id, cardId, error) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE__FAILURE,
payload: {
id,
cardId,
error,
},
});
export const handleUserFromCardRemove = (cardMembership) => ({
type: ActionTypes.USER_FROM_CARD_REMOVE_HANDLE,
payload: {
cardMembership,
},
});
export const addUserToBoardFilter = (id, boardId) => ({
type: ActionTypes.USER_TO_BOARD_FILTER_ADD,
payload: {
id,
boardId,
},
});
export const removeUserFromBoardFilter = (id, boardId) => ({
type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE,
payload: {
id,
boardId,
},
});
+9
View File
@@ -0,0 +1,9 @@
import http from './http';
/* Actions */
const createAccessToken = (data) => http.post('/access-tokens', data);
export default {
createAccessToken,
};
+36
View File
@@ -0,0 +1,36 @@
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,
};
+51
View File
@@ -0,0 +1,51 @@
import http from './http';
import socket from './socket';
/* Transformers */
export const transformAttachment = (attachment) => ({
...attachment,
createdAt: new Date(attachment.createdAt),
});
/* Actions */
const createAttachment = (cardId, data, requestId) =>
http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data).then((body) => ({
...body,
item: transformAttachment(body.item),
}));
const updateAttachment = (id, data) =>
socket.patch(`/attachments/${id}`, data).then((body) => ({
...body,
item: transformAttachment(body.item),
}));
const deleteAttachment = (id) =>
socket.delete(`/attachments/${id}`).then((body) => ({
...body,
item: transformAttachment(body.item),
}));
/* Event handlers */
const makeHandleAttachmentCreate = (next) => (body) => {
next({
...body,
item: transformAttachment(body.item),
});
};
const makeHandleAttachmentUpdate = makeHandleAttachmentCreate;
const makeHandleAttachmentDelete = makeHandleAttachmentCreate;
export default {
createAttachment,
updateAttachment,
deleteAttachment,
makeHandleAttachmentCreate,
makeHandleAttachmentUpdate,
makeHandleAttachmentDelete,
};
+13
View File
@@ -0,0 +1,13 @@
import socket from './socket';
/* Actions */
const createBoardMembership = (boardId, data) =>
socket.post(`/boards/${boardId}/memberships`, data);
const deleteBoardMembership = (id) => socket.delete(`/board-memberships/${id}`);
export default {
createBoardMembership,
deleteBoardMembership,
};
+28
View File
@@ -0,0 +1,28 @@
import socket from './socket';
import { transformCard } from './cards';
import { transformAttachment } from './attachments';
/* Actions */
const createBoard = (projectId, data) => socket.post(`/projects/${projectId}/boards`, data);
const getBoard = (id) =>
socket.get(`/boards/${id}`).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
attachments: body.included.attachments.map(transformAttachment),
},
}));
const updateBoard = (id, data) => socket.patch(`/boards/${id}`, data);
const deleteBoard = (id) => socket.delete(`/boards/${id}`);
export default {
createBoard,
getBoard,
updateBoard,
deleteBoard,
};
+12
View File
@@ -0,0 +1,12 @@
import socket from './socket';
/* Actions */
const createCardLabel = (cardId, data) => socket.post(`/cards/${cardId}/labels`, data);
const deleteCardLabel = (cardId, labelId) => socket.delete(`/cards/${cardId}/labels/${labelId}`);
export default {
createCardLabel,
deleteCardLabel,
};
+13
View File
@@ -0,0 +1,13 @@
import socket from './socket';
/* Actions */
const createCardMembership = (cardId, data) => socket.post(`/cards/${cardId}/memberships`, data);
const deleteCardMembership = (cardId, userId) =>
socket.delete(`/cards/${cardId}/memberships?userId=${userId}`);
export default {
createCardMembership,
deleteCardMembership,
};
+99
View File
@@ -0,0 +1,99 @@
import socket from './socket';
import { transformAttachment } from './attachments';
/* Transformers */
export const transformCard = (card) => ({
...card,
...(card.dueDate && {
dueDate: new Date(card.dueDate),
}),
...(card.timer && {
timer: {
...card.timer,
...(card.timer.startedAt && {
startedAt: new Date(card.timer.startedAt),
}),
},
}),
});
export const transformCardData = (data) => ({
...data,
...(data.dueDate && {
dueDate: data.dueDate.toISOString(),
}),
...(data.timer && {
timer: {
...data.timer,
...(data.timer.startedAt && {
startedAt: data.timer.startedAt.toISOString(),
}),
},
}),
});
/* Actions */
const getCards = (boardId, data) =>
socket.get(`/board/${boardId}/cards`, data).then((body) => ({
...body,
items: body.items.map(transformCard),
included: {
...body.included,
attachments: body.included.attachments.map(transformAttachment),
},
}));
const createCard = (boardId, data) =>
socket.post(`/boards/${boardId}/cards`, transformCardData(data)).then((body) => ({
...body,
item: transformCard(body.item),
}));
const getCard = (id) =>
socket.get(`/cards/${id}`).then((body) => ({
...body,
item: transformCard(body.item),
}));
const updateCard = (id, data) =>
socket.patch(`/cards/${id}`, transformCardData(data)).then((body) => ({
...body,
item: transformCard(body.item),
}));
const deleteCard = (id) =>
socket.delete(`/cards/${id}`).then((body) => ({
...body,
item: transformCard(body.item),
}));
/* Event handlers */
const makeHandleCardCreate = (next) => (body) => {
next({
...body,
item: transformCard(body.item),
});
};
const makeHandleCardUpdate = (next) => (body) => {
next({
...body,
item: transformCard(body.item),
});
};
const makeHandleCardDelete = makeHandleCardUpdate;
export default {
getCards,
createCard,
getCard,
updateCard,
deleteCard,
makeHandleCardCreate,
makeHandleCardUpdate,
makeHandleCardDelete,
};
+28
View File
@@ -0,0 +1,28 @@
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,
};
+37
View File
@@ -0,0 +1,37 @@
import { fetch } from 'whatwg-fetch';
import Config from '../constants/Config';
const http = {};
// TODO: add all methods
['POST'].forEach((method) => {
http[method.toLowerCase()] = (url, data) => {
const formData = Object.keys(data).reduce((result, key) => {
result.append(key, data[key]);
return result;
}, new FormData());
return fetch(`${Config.SERVER_BASE_URL}/api${url}`, {
method,
body: formData,
...Config.FETCH_OPTIONS,
})
.then((response) =>
response.json().then((body) => ({
body,
isError: response.status !== 200,
})),
)
.then(({ body, isError }) => {
if (isError) {
throw body;
}
return body;
});
};
});
export default http;
+39
View File
@@ -0,0 +1,39 @@
import http from './http';
import socket from './socket';
import accessTokens from './access-tokens';
import users from './users';
import projects from './projects';
import projectManagers from './project-managers';
import boards from './boards';
import boardMemberships from './board-memberships';
import labels from './labels';
import lists from './lists';
import cards from './cards';
import cardMemberships from './card-memberships';
import cardLabels from './card-labels';
import tasks from './tasks';
import attachments from './attachments';
import actions from './actions';
import commentActions from './comment-actions';
import notifications from './notifications';
export { http, socket };
export default {
...accessTokens,
...users,
...projects,
...projectManagers,
...boards,
...boardMemberships,
...labels,
...lists,
...cards,
...cardMemberships,
...cardLabels,
...tasks,
...attachments,
...actions,
...commentActions,
...notifications,
};
+15
View File
@@ -0,0 +1,15 @@
import socket from './socket';
/* Actions */
const createLabel = (boardId, data) => socket.post(`/boards/${boardId}/labels`, data);
const updateLabel = (id, data) => socket.patch(`/labels/${id}`, data);
const deleteLabel = (id) => socket.delete(`/labels/${id}`);
export default {
createLabel,
updateLabel,
deleteLabel,
};
+15
View File
@@ -0,0 +1,15 @@
import socket from './socket';
/* Actions */
const createList = (boardId, data) => socket.post(`/boards/${boardId}/lists`, data);
const updateList = (id, data) => socket.patch(`/lists/${id}`, data);
const deleteList = (id) => socket.delete(`/lists/${id}`);
export default {
createList,
updateList,
deleteList,
};
+33
View File
@@ -0,0 +1,33 @@
import socket from './socket';
import { transformCard } from './cards';
import { transformAction } from './actions';
/* Actions */
const getNotifications = () =>
socket.get('/notifications').then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
actions: body.included.actions.map(transformAction),
},
}));
const getNotification = (id) =>
socket.get(`/notifications/${id}`).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
actions: body.included.actions.map(transformAction),
},
}));
const updateNotifications = (ids, data) => socket.patch(`/notifications/${ids.join(',')}`, data);
export default {
getNotifications,
getNotification,
updateNotifications,
};
+13
View File
@@ -0,0 +1,13 @@
import socket from './socket';
/* Actions */
const createProjectManager = (projectId, data) =>
socket.post(`/projects/${projectId}/managers`, data);
const deleteProjectManager = (id) => socket.delete(`/project-managers/${id}`);
export default {
createProjectManager,
deleteProjectManager,
};
+26
View File
@@ -0,0 +1,26 @@
import http from './http';
import socket from './socket';
/* Actions */
const getProjects = () => socket.get('/projects');
const createProject = (data) => socket.post('/projects', data);
const getProject = (id) => socket.get(`/projects/${id}`);
const updateProject = (id, data) => socket.patch(`/projects/${id}`, data);
const updateProjectBackgroundImage = (id, data) =>
http.post(`/projects/${id}/background-image`, data);
const deleteProject = (id) => socket.delete(`/projects/${id}`);
export default {
getProjects,
createProject,
getProject,
updateProject,
updateProjectBackgroundImage,
deleteProject,
};
+38
View File
@@ -0,0 +1,38 @@
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
import Config from '../constants/Config';
const io = sailsIOClient(socketIOClient);
io.sails.url = Config.SERVER_BASE_URL;
io.sails.autoConnect = false;
io.sails.reconnection = true;
io.sails.useCORSRouteToGetCookie = false;
io.sails.environment = process.env.NODE_ENV;
const { socket } = io;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
socket[method.toLowerCase()] = (url, data) =>
new Promise((resolve, reject) => {
socket.request(
{
method,
data,
url: `/api${url}`,
},
(_, { body, error }) => {
if (error) {
reject(body);
} else {
resolve(body);
}
},
);
});
});
export default socket;
+15
View File
@@ -0,0 +1,15 @@
import socket from './socket';
/* Actions */
const createTask = (cardId, data) => socket.post(`/cards/${cardId}/tasks`, data);
const updateTask = (id, data) => socket.patch(`/tasks/${id}`, data);
const deleteTask = (id) => socket.delete(`/tasks/${id}`);
export default {
createTask,
updateTask,
deleteTask,
};
+37
View File
@@ -0,0 +1,37 @@
import http from './http';
import socket from './socket';
/* Actions */
const getUsers = () => socket.get('/users');
const createUser = (data) => socket.post('/users', data);
const getUser = (id) => socket.get(`/users/${id}`);
const getCurrentUser = () => socket.get('/users/me');
const updateUser = (id, data) => socket.patch(`/users/${id}`, data);
const updateUserEmail = (id, data) => socket.patch(`/users/${id}/email`, data);
const updateUserPassword = (id, data) => socket.patch(`/users/${id}/password`, data);
const updateUserUsername = (id, data) => socket.patch(`/users/${id}/username`, data);
const updateUserAvatar = (id, data) => http.post(`/users/${id}/avatar`, data);
const deleteUser = (id) => socket.delete(`/users/${id}`);
export default {
getUsers,
createUser,
getUser,
getCurrentUser,
updateUser,
updateUserEmail,
updateUserPassword,
updateUserUsername,
updateUserAvatar,
deleteUser,
};

Before

Width:  |  Height:  |  Size: 559 KiB

After

Width:  |  Height:  |  Size: 559 KiB

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 432 B

Before

Width:  |  Height:  |  Size: 142 B

After

Width:  |  Height:  |  Size: 142 B

@@ -0,0 +1,38 @@
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { ProjectBackgroundTypes } from '../../constants/Enums';
import styles from './Background.module.scss';
import globalStyles from '../../styles.module.scss';
function Background({ type, name, imageUrl }) {
return (
<div
className={classNames(
styles.wrapper,
type === ProjectBackgroundTypes.GRADIENT &&
globalStyles[`background${upperFirst(camelCase(name))}`],
)}
style={{
background: type === 'image' && `url("${imageUrl}") center / cover`,
}}
/>
);
}
Background.propTypes = {
type: PropTypes.string.isRequired,
name: PropTypes.string,
imageUrl: PropTypes.string,
};
Background.defaultProps = {
name: undefined,
imageUrl: undefined,
};
export default Background;
@@ -0,0 +1,8 @@
:global(#app) {
.wrapper {
height: 100%;
position: fixed;
width: 100%;
z-index: -1;
}
}
@@ -0,0 +1,3 @@
import Background from './Background';
export default Background;
@@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import Filters from './Filters';
import Memberships from '../Memberships';
import styles from './BoardActions.module.scss';
const BoardActions = React.memo(
({
memberships,
labels,
filterUsers,
filterLabels,
allUsers,
canEditMemberships,
onMembershipCreate,
onMembershipDelete,
onUserToFilterAdd,
onUserFromFilterRemove,
onLabelToFilterAdd,
onLabelFromFilterRemove,
onLabelCreate,
onLabelUpdate,
onLabelDelete,
}) => {
return (
<div className={styles.actions}>
<div className={styles.action}>
<Memberships
items={memberships}
allUsers={allUsers}
canEdit={canEditMemberships}
onCreate={onMembershipCreate}
onDelete={onMembershipDelete}
/>
</div>
<div className={styles.action}>
<Filters
users={filterUsers}
labels={filterLabels}
allBoardMemberships={memberships}
allLabels={labels}
onUserAdd={onUserToFilterAdd}
onUserRemove={onUserFromFilterRemove}
onLabelAdd={onLabelToFilterAdd}
onLabelRemove={onLabelFromFilterRemove}
onLabelCreate={onLabelCreate}
onLabelUpdate={onLabelUpdate}
onLabelDelete={onLabelDelete}
/>
</div>
</div>
);
},
);
BoardActions.propTypes = {
/* eslint-disable react/forbid-prop-types */
memberships: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired,
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
canEditMemberships: PropTypes.bool.isRequired,
onMembershipCreate: PropTypes.func.isRequired,
onMembershipDelete: PropTypes.func.isRequired,
onUserToFilterAdd: PropTypes.func.isRequired,
onUserFromFilterRemove: PropTypes.func.isRequired,
onLabelToFilterAdd: PropTypes.func.isRequired,
onLabelFromFilterRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
};
export default BoardActions;
@@ -0,0 +1,11 @@
:global(#app) {
.action {
margin-right: 20px;
}
.actions {
align-items: center;
display: flex;
margin: 20px 20px;
}
}
@@ -0,0 +1,120 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import User from '../User';
import Label from '../Label';
import BoardMembershipsPopup from '../BoardMembershipsPopup';
import LabelsPopup from '../LabelsPopup';
import styles from './Filters.module.scss';
const Filters = React.memo(
({
users,
labels,
allBoardMemberships,
allLabels,
onUserAdd,
onUserRemove,
onLabelAdd,
onLabelRemove,
onLabelCreate,
onLabelUpdate,
onLabelDelete,
}) => {
const [t] = useTranslation();
const handleRemoveUserClick = useCallback(
(id) => {
onUserRemove(id);
},
[onUserRemove],
);
const handleRemoveLabelClick = useCallback(
(id) => {
onLabelRemove(id);
},
[onLabelRemove],
);
return (
<>
<span className={styles.filter}>
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={users.map((user) => user.id)}
title={t('common.filterByMembers', {
context: 'title',
})}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<button type="button" className={styles.filterButton}>
<span className={styles.filterTitle}>{`${t('common.members')}:`}</span>
{users.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
</button>
</BoardMembershipsPopup>
{users.map((user) => (
<span key={user.id} className={styles.filterItem}>
<User
name={user.name}
avatarUrl={user.avatarUrl}
size="tiny"
onClick={() => handleRemoveUserClick(user.id)}
/>
</span>
))}
</span>
<span className={styles.filter}>
<LabelsPopup
items={allLabels}
currentIds={labels.map((label) => label.id)}
title={t('common.filterByLabels', {
context: 'title',
})}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<button type="button" className={styles.filterButton}>
<span className={styles.filterTitle}>{`${t('common.labels')}:`}</span>
{labels.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
</button>
</LabelsPopup>
{labels.map((label) => (
<span key={label.id} className={styles.filterItem}>
<Label
name={label.name}
color={label.color}
size="small"
onClick={() => handleRemoveLabelClick(label.id)}
/>
</span>
))}
</span>
</>
);
},
);
Filters.propTypes = {
/* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
onLabelAdd: PropTypes.func.isRequired,
onLabelRemove: PropTypes.func.isRequired,
onLabelCreate: PropTypes.func.isRequired,
onLabelUpdate: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
};
export default Filters;
@@ -0,0 +1,46 @@
:global(#app) {
.filter {
margin-right: 10px;
}
.filterButton {
background: transparent;
border: none;
cursor: pointer;
display: inline-block;
outline: none;
padding: 0;
}
.filterItem {
display: inline-block;
font-size: 0;
line-height: 0;
margin-right: 4px;
max-width: 190px;
vertical-align: top;
}
.filterLabel {
background: rgba(0, 0, 0, 0.24);
border-radius: 3px;
color: #fff;
display: inline-block;
font-size: 12px;
line-height: 20px;
padding: 2px 8px;
&:hover {
background: rgba(0, 0, 0, 0.32);
}
}
.filterTitle {
border-radius: 3px;
color: #fff;
display: inline-block;
font-size: 12px;
line-height: 20px;
padding: 2px 12px;
}
}
@@ -0,0 +1,3 @@
import BoardActions from './BoardActions';
export default BoardActions;
+178
View File
@@ -0,0 +1,178 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { closePopup } from '../../lib/popup';
import DroppableTypes from '../../constants/DroppableTypes';
import ListContainer from '../../containers/ListContainer';
import CardModalContainer from '../../containers/CardModalContainer';
import ListAdd from './ListAdd';
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
import styles from './BoardKanban.module.scss';
const parseDndId = (dndId) => dndId.split(':')[1];
const BoardKanban = React.memo(
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
const [t] = useTranslation();
const [isListAddOpened, setIsListAddOpened] = useState(false);
const wrapper = useRef(null);
const prevPosition = useRef(null);
const handleAddListClick = useCallback(() => {
setIsListAddOpened(true);
}, []);
const handleAddListClose = useCallback(() => {
setIsListAddOpened(false);
}, []);
const handleDragStart = useCallback(() => {
closePopup();
}, []);
const handleDragEnd = useCallback(
({ draggableId, type, source, destination }) => {
if (
!destination ||
(source.droppableId === destination.droppableId && source.index === destination.index)
) {
return;
}
const id = parseDndId(draggableId);
switch (type) {
case DroppableTypes.LIST:
onListMove(id, destination.index);
break;
case DroppableTypes.CARD:
onCardMove(id, parseDndId(destination.droppableId), destination.index);
break;
default:
}
},
[onListMove, onCardMove],
);
const handleMouseDown = useCallback(
(event) => {
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
return;
}
prevPosition.current = event.clientX;
},
[wrapper],
);
const handleWindowMouseMove = useCallback(
(event) => {
if (!prevPosition.current) {
return;
}
event.preventDefault();
window.scrollBy({
left: prevPosition.current - event.clientX,
});
prevPosition.current = event.clientX;
},
[prevPosition],
);
const handleWindowMouseUp = useCallback(() => {
prevPosition.current = null;
}, [prevPosition]);
useEffect(() => {
document.body.style.overflowX = 'auto';
return () => {
document.body.style.overflowX = null;
};
}, []);
useEffect(() => {
if (isListAddOpened) {
window.scroll(document.body.scrollWidth, 0);
}
}, [listIds, isListAddOpened]);
useEffect(() => {
window.addEventListener('mouseup', handleWindowMouseUp);
window.addEventListener('mousemove', handleWindowMouseMove);
return () => {
window.removeEventListener('mouseup', handleWindowMouseUp);
window.removeEventListener('mousemove', handleWindowMouseMove);
};
}, [handleWindowMouseUp, handleWindowMouseMove]);
return (
<>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
<div>
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
{({ innerRef, droppableProps, placeholder }) => (
<div
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
data-drag-scroller
ref={innerRef}
className={styles.lists}
>
{listIds.map((listId, index) => (
<ListContainer key={listId} id={listId} index={index} />
))}
{placeholder}
{canEdit && (
<div data-drag-scroller className={styles.list}>
{isListAddOpened ? (
<ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
) : (
<button
type="button"
className={styles.addListButton}
onClick={handleAddListClick}
>
<PlusMathIcon className={styles.addListButtonIcon} />
<span className={styles.addListButtonText}>
{listIds.length > 0
? t('action.addAnotherList')
: t('action.addList')}
</span>
</button>
)}
</div>
)}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
{isCardModalOpened && <CardModalContainer />}
</>
);
},
);
BoardKanban.propTypes = {
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isCardModalOpened: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onListCreate: PropTypes.func.isRequired,
onListMove: PropTypes.func.isRequired,
onCardMove: PropTypes.func.isRequired,
};
export default BoardKanban;
@@ -0,0 +1,64 @@
:global(#app) {
.addListButton {
background: rgba(0, 0, 0, 0.24);
border: none;
border-radius: 3px;
color: rgba(255, 255, 255, 0.72);
cursor: pointer;
display: block;
fill: rgba(255, 255, 255, 0.72);
font-weight: normal;
height: 42px;
padding: 11px;
text-align: left;
transition: background 85ms ease-in, opacity 40ms ease-in,
border-color 85ms ease-in;
width: 100%;
&:active {
outline: none;
}
&:hover {
background: rgba(0, 0, 0, 0.32);
}
}
.addListButtonIcon {
height: 20px;
padding: 0.64px;
width: 20px;
}
.addListButtonText {
display: inline-block;
font-size: 14px;
line-height: 20px;
vertical-align: top;
}
.list {
margin-right: 20px;
width: 272px;
}
.lists {
display: inline-flex;
height: 100%;
min-width: 100%;
}
.panel {
align-items: center;
display: flex;
margin-bottom: 20px;
}
.panelItem {
margin-right: 20px;
}
.wrapper {
margin: 0 20px;
}
}
+89
View File
@@ -0,0 +1,89 @@
import React, { useCallback, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Input } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../lib/hooks';
import { useClosableForm, useForm } from '../../hooks';
import styles from './ListAdd.module.scss';
const DEFAULT_DATA = {
name: '',
};
const ListAdd = React.memo(({ onCreate, onClose }) => {
const [t] = useTranslation();
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [selectNameFieldState, selectNameField] = useToggle();
const nameField = useRef(null);
const handleFieldKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
onClose();
}
},
[onClose],
);
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
const handleSubmit = useCallback(() => {
const cleanData = {
...data,
name: data.name.trim(),
};
if (!cleanData.name) {
nameField.current.select();
return;
}
onCreate(cleanData);
setData(DEFAULT_DATA);
selectNameField();
}, [onCreate, data, setData, selectNameField]);
useEffect(() => {
nameField.current.select();
}, []);
useDidUpdate(() => {
nameField.current.select();
}, [selectNameFieldState]);
return (
<Form className={styles.wrapper} onSubmit={handleSubmit}>
<Input
ref={nameField}
name="name"
value={data.name}
placeholder={t('common.enterListTitle')}
className={styles.field}
onKeyDown={handleFieldKeyDown}
onChange={handleFieldChange}
onBlur={handleFieldBlur}
/>
<div className={styles.controls}>
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
<Button
positive
content={t('action.addList')}
className={styles.submitButton}
onMouseOver={handleControlMouseOver}
onMouseOut={handleControlMouseOut}
/>
</div>
</Form>
);
});
ListAdd.propTypes = {
onCreate: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default ListAdd;
@@ -0,0 +1,33 @@
:global(#app) {
.controls {
margin-top: 4px;
}
.field {
border: none;
border-radius: 3px;
box-shadow: 0 1px 0 #ccc;
color: #333;
outline: none;
overflow: hidden;
width: 100%;
&:focus {
border-color: #298fca;
box-shadow: 0 0 2px #298fca;
}
}
.submitButton {
min-height: 30px;
vertical-align: top;
}
.wrapper {
background: #e2e4e6;
border-radius: 3px;
padding: 4px;
transition: opacity 40ms ease-in;
width: 272px;
}
}
+3
View File
@@ -0,0 +1,3 @@
import BoardKanban from './BoardKanban';
export default BoardKanban;
@@ -0,0 +1,5 @@
import { withPopup } from '../lib/popup';
import BoardMembershipsStep from './BoardMembershipsStep';
export default withPopup(BoardMembershipsStep);
@@ -0,0 +1,67 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import Item from './Item';
import styles from './BoardMembershipsStep.module.scss';
const BoardMembershipsStep = React.memo(
({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => {
const [t] = useTranslation();
const handleUserSelect = useCallback(
(id) => {
onUserSelect(id);
},
[onUserSelect],
);
const handleUserDeselect = useCallback(
(id) => {
onUserDeselect(id);
},
[onUserDeselect],
);
return (
<>
<Popup.Header onBack={onBack}>{t(title)}</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
{items.map((item) => (
<Item
key={item.id}
isPersisted={item.isPersisted}
isActive={currentUserIds.includes(item.user.id)}
user={item.user}
onUserSelect={() => handleUserSelect(item.user.id)}
onUserDeselect={() => handleUserDeselect(item.user.id)}
/>
))}
</Menu>
</Popup.Content>
</>
);
},
);
BoardMembershipsStep.propTypes = {
/* eslint-disable react/forbid-prop-types */
items: PropTypes.array.isRequired,
currentUserIds: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
title: PropTypes.string,
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func.isRequired,
onBack: PropTypes.func,
};
BoardMembershipsStep.defaultProps = {
title: 'common.members',
onBack: undefined,
};
export default BoardMembershipsStep;
@@ -0,0 +1,6 @@
:global(#app) {
.menu {
margin: -7px auto -5px;
width: 100%;
}
}
+44
View File
@@ -0,0 +1,44 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Menu } from 'semantic-ui-react';
import User from '../User';
import styles from './Item.module.scss';
const Item = React.memo(({ isPersisted, isActive, user, onUserSelect, onUserDeselect }) => {
const handleToggleClick = useCallback(() => {
if (isActive) {
onUserDeselect();
} else {
onUserSelect();
}
}, [isActive, onUserSelect, onUserDeselect]);
return (
<Menu.Item
active={isActive}
disabled={!isPersisted}
className={classNames(styles.menuItem, isActive && styles.menuItemActive)}
onClick={handleToggleClick}
>
<span className={styles.user}>
<User name={user.name} avatarUrl={user.avatarUrl} />
</span>
<div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>
{user.name}
</div>
</Menu.Item>
);
});
Item.propTypes = {
isPersisted: PropTypes.bool.isRequired,
isActive: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func.isRequired,
};
export default Item;
@@ -0,0 +1,42 @@
:global(#app) {
.menuItem {
display: block;
margin: 0;
padding: 4px;
}
.menuItemActive {
background: transparent;
&:hover {
background: rgba(0, 0, 0, 0.05);
}
}
.menuItemText {
display: inline-block;
line-height: 32px;
width: calc(100% - 40px);
}
.menuItemTextActive:before {
bottom: 2px;
color: #798d99;
content: "Г";
font-size: 18px;
font-weight: normal;
line-height: 36px;
position: absolute;
right: 2px;
text-align: center;
transform: rotate(-135deg);
width: 36px;
}
.user {
display: inline-block;
line-height: 32px;
padding-right: 8px;
width: 40px;
}
}
+3
View File
@@ -0,0 +1,3 @@
import BoardMembershipsStep from './BoardMembershipsStep';
export default BoardMembershipsStep;

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