Polished existing features, added items to sidebar navigation

This commit is contained in:
ireic
2019-12-19 22:31:31 +01:00
parent 32170e90d2
commit 6809ec494a
111 changed files with 649 additions and 484 deletions

View File

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 384 KiB

View File

Before

Width:  |  Height:  |  Size: 433 KiB

After

Width:  |  Height:  |  Size: 433 KiB

View File

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 341 KiB

View File

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 432 KiB

View File

@@ -28,7 +28,12 @@
<glyph unicode="&#xe912;" glyph-name="trash" d="M768 640v-554.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-426.667c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v554.667zM725.333 725.334v42.667c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504h-170.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-42.667h-170.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h42.667v-554.667c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h426.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v554.667h42.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM384 725.334v42.667c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h170.667c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-42.667zM384 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM554.667 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667z" /> <glyph unicode="&#xe912;" glyph-name="trash" d="M768 640v-554.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-426.667c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v554.667zM725.333 725.334v42.667c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504h-170.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-42.667h-170.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h42.667v-554.667c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h426.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v554.667h42.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM384 725.334v42.667c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h170.667c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-42.667zM384 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM554.667 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667z" />
<glyph unicode="&#xe913;" glyph-name="close" d="M225.835 652.502l225.835-225.835-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l225.835 225.835 225.835-225.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-225.835 225.835 225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835-225.835 225.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" /> <glyph unicode="&#xe913;" glyph-name="close" d="M225.835 652.502l225.835-225.835-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l225.835 225.835 225.835-225.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-225.835 225.835 225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835-225.835 225.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" />
<glyph unicode="&#xe914;" glyph-name="stopwatch" d="M512 84.667q124 0 211 88t87 212-87 211-211 87-211-87-87-211 87-212 211-88zM812 622.667q34-44 59-113t25-125q0-158-112-271t-272-113-272 113-112 271 112 271 272 113q54 0 125-26t115-60l60 62q32-26 60-60zM470 340.667v256h84v-256h-84zM640 896.667v-86h-256v86h256z" /> <glyph unicode="&#xe914;" glyph-name="stopwatch" d="M512 84.667q124 0 211 88t87 212-87 211-211 87-211-87-87-211 87-212 211-88zM812 622.667q34-44 59-113t25-125q0-158-112-271t-272-113-272 113-112 271 112 271 272 113q54 0 125-26t115-60l60 62q32-26 60-60zM470 340.667v256h84v-256h-84zM640 896.667v-86h-256v86h256z" />
<glyph unicode="&#xe915;" glyph-name="github" horiz-adv-x="878" d="M438.857 877.714c242.286 0 438.857-196.571 438.857-438.857 0-193.714-125.714-358.286-300-416.571-22.286-4-30.286 9.714-30.286 21.143 0 14.286 0.571 61.714 0.571 120.571 0 41.143-13.714 67.429-29.714 81.143 97.714 10.857 200.571 48 200.571 216.571 0 48-17.143 86.857-45.143 117.714 4.571 11.429 19.429 56-4.571 116.571-36.571 11.429-120.571-45.143-120.571-45.143-34.857 9.714-72.571 14.857-109.714 14.857s-74.857-5.143-109.714-14.857c0 0-84 56.571-120.571 45.143-24-60.571-9.143-105.143-4.571-116.571-28-30.857-45.143-69.714-45.143-117.714 0-168 102.286-205.714 200-216.571-12.571-11.429-24-30.857-28-58.857-25.143-11.429-89.143-30.857-127.429 36.571-24 41.714-67.429 45.143-67.429 45.143-42.857 0.571-2.857-26.857-2.857-26.857 28.571-13.143 48.571-64 48.571-64 25.714-78.286 148-52 148-52 0-36.571 0.571-70.857 0.571-81.714 0-11.429-8-25.143-30.286-21.143-174.286 58.286-300 222.857-300 416.571 0 242.286 196.571 438.857 438.857 438.857zM166.286 247.428c1.143 2.286-0.571 5.143-4 6.857-3.429 1.143-6.286 0.571-7.429-1.143-1.143-2.286 0.571-5.143 4-6.857 2.857-1.714 6.286-1.143 7.429 1.143zM184 228c2.286 1.714 1.714 5.714-1.143 9.143-2.857 2.857-6.857 4-9.143 1.714-2.286-1.714-1.714-5.714 1.143-9.143 2.857-2.857 6.857-4 9.143-1.714zM201.143 202.286c2.857 2.286 2.857 6.857 0 10.857-2.286 4-6.857 5.714-9.714 3.429-2.857-1.714-2.857-6.286 0-10.286s7.429-5.714 9.714-4zM225.143 178.286c2.286 2.286 1.143 7.429-2.286 10.857-4 4-9.143 4.571-11.429 1.714-2.857-2.286-1.714-7.429 2.286-10.857 4-4 9.143-4.571 11.429-1.714zM257.714 164c1.143 3.429-2.286 7.429-7.429 9.143-4.571 1.143-9.714-0.571-10.857-4s2.286-7.429 7.429-8.571c4.571-1.714 9.714 0 10.857 3.429zM293.714 161.143c0 4-4.571 6.857-9.714 6.286-5.143 0-9.143-2.857-9.143-6.286 0-4 4-6.857 9.714-6.286 5.143 0 9.143 2.857 9.143 6.286zM326.857 166.857c-0.571 3.429-5.143 5.714-10.286 5.143-5.143-1.143-8.571-4.571-8-8.571 0.571-3.429 5.143-5.714 10.286-4.571s8.571 4.571 8 8z" />
<glyph unicode="&#xe916;" glyph-name="menu" d="M128 384h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM128 640h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM128 128h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" /> <glyph unicode="&#xe916;" glyph-name="menu" d="M128 384h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM128 640h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM128 128h768c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-768c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe917;" glyph-name="arrow-left-circle" d="M981.333 426.667c0 129.579-52.565 246.997-137.472 331.861s-202.283 137.472-331.861 137.472-246.997-52.565-331.861-137.472-137.472-202.283-137.472-331.861 52.565-246.997 137.472-331.861 202.283-137.472 331.861-137.472 246.997 52.565 331.861 137.472 137.472 202.283 137.472 331.861zM896 426.667c0-106.069-42.923-201.984-112.469-271.531s-165.461-112.469-271.531-112.469-201.984 42.923-271.531 112.469-112.469 165.461-112.469 271.531 42.923 201.984 112.469 271.531 165.461 112.469 271.531 112.469 201.984-42.923 271.531-112.469 112.469-165.461 112.469-271.531zM682.667 469.334h-238.336l97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-170.667-170.667c-4.096-4.096-7.168-8.789-9.259-13.824s-3.243-10.539-3.243-16.341c0-5.547 1.067-11.136 3.243-16.341 2.091-5.035 5.163-9.728 9.259-13.824l170.667-170.667c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835h238.336c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667z" /> <glyph unicode="&#xe917;" glyph-name="arrow-left-circle" d="M981.333 426.667c0 129.579-52.565 246.997-137.472 331.861s-202.283 137.472-331.861 137.472-246.997-52.565-331.861-137.472-137.472-202.283-137.472-331.861 52.565-246.997 137.472-331.861 202.283-137.472 331.861-137.472 246.997 52.565 331.861 137.472 137.472 202.283 137.472 331.861zM896 426.667c0-106.069-42.923-201.984-112.469-271.531s-165.461-112.469-271.531-112.469-201.984 42.923-271.531 112.469-112.469 165.461-112.469 271.531 42.923 201.984 112.469 271.531 165.461 112.469 271.531 112.469 201.984-42.923 271.531-112.469 112.469-165.461 112.469-271.531zM682.667 469.334h-238.336l97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-170.667-170.667c-4.096-4.096-7.168-8.789-9.259-13.824s-3.243-10.539-3.243-16.341c0-5.547 1.067-11.136 3.243-16.341 2.091-5.035 5.163-9.728 9.259-13.824l170.667-170.667c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835h238.336c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667z" />
<glyph unicode="&#xe918;" glyph-name="feedback" d="M979.755 841.856c1.835 6.443 2.133 13.397 0.64 20.309-1.024 4.821-2.901 9.557-5.589 13.867-2.731 4.352-6.187 8.107-10.155 11.179-4.992 3.84-10.624 6.4-16.469 7.723s-12.032 1.451-18.176 0.171c-1.792-0.384-3.627-0.896-5.376-1.493l-0.896-0.299-852.48-298.368c-10.752-3.755-19.925-11.776-24.917-22.955-9.557-21.547 0.128-46.763 21.675-56.32l369.024-164.011 164.011-369.024c4.608-10.368 13.355-18.901 24.875-22.955 22.229-7.765 46.592 3.925 54.357 26.197l298.368 852.437c0.427 1.152 0.811 2.304 1.152 3.499zM459.904 434.902l-258.901 115.029 575.275 201.387zM836.651 690.944l-201.387-575.275-115.029 258.901z" /> <glyph unicode="&#xe918;" glyph-name="feedback" d="M979.755 841.856c1.835 6.443 2.133 13.397 0.64 20.309-1.024 4.821-2.901 9.557-5.589 13.867-2.731 4.352-6.187 8.107-10.155 11.179-4.992 3.84-10.624 6.4-16.469 7.723s-12.032 1.451-18.176 0.171c-1.792-0.384-3.627-0.896-5.376-1.493l-0.896-0.299-852.48-298.368c-10.752-3.755-19.925-11.776-24.917-22.955-9.557-21.547 0.128-46.763 21.675-56.32l369.024-164.011 164.011-369.024c4.608-10.368 13.355-18.901 24.875-22.955 22.229-7.765 46.592 3.925 54.357 26.197l298.368 852.437c0.427 1.152 0.811 2.304 1.152 3.499zM459.904 434.902l-258.901 115.029 575.275 201.387zM836.651 690.944l-201.387-575.275-115.029 258.901z" />
<glyph unicode="&#xe919;" glyph-name="page" d="M597.333 896h-341.333c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-682.667c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h512c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v512c0 11.776-4.779 22.443-12.501 30.165l-256 256c-4.096 4.096-8.789 7.168-13.824 9.259-5.205 2.176-10.795 3.243-16.341 3.243zM750.336 640h-110.336v110.336zM554.667 810.667v-213.333c0-23.552 19.115-42.667 42.667-42.667h213.333v-469.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-512c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v682.667c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM682.667 426.667h-341.333c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h341.333c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM682.667 256h-341.333c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h341.333c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM426.667 597.334h-85.333c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h85.333c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667z" />
<glyph unicode="&#xe91a;" glyph-name="component" d="M618.965 537.387l-106.965-61.867-297.003 171.819 107.136 61.227zM809.003 647.339l-104.789-60.629-296.277 170.88 82.517 47.147c4.779 2.731 9.899 4.48 15.147 5.333 9.301 1.451 18.987-0.128 27.904-5.291zM491.776-41.002c6.016-3.243 12.928-5.077 20.224-5.077 7.381 0 14.336 1.877 20.395 5.163 15.189 2.475 29.909 7.68 43.392 15.36l298.709 170.709c26.368 15.232 45.269 38.315 55.424 64.597 5.675 14.592 8.619 30.165 8.747 46.251v341.333c0 20.395-4.821 39.723-13.397 56.917-0.939 3.029-2.219 5.973-3.883 8.832-1.963 3.371-4.267 6.357-6.912 8.96-1.323 1.835-2.731 3.669-4.139 5.419-9.813 12.203-21.845 22.528-35.456 30.507l-299.051 170.88c-26.027 15.019-55.467 19.84-83.328 15.531-15.531-2.432-30.507-7.637-44.288-15.488l-136.491-77.995c-8.96-1.749-17.323-6.4-23.595-13.483l-138.624-79.232c-16.341-9.429-29.824-21.888-40.149-36.267-2.56-2.56-4.864-5.547-6.784-8.832-1.664-2.901-2.987-5.888-3.925-8.96-1.707-3.456-3.243-6.955-4.608-10.496-5.632-14.635-8.576-30.208-8.704-45.995v-341.632c0.043-30.293 10.581-58.197 28.331-80.128 9.813-12.203 21.845-22.528 35.456-30.507l299.051-170.88c13.824-7.979 28.587-13.099 43.605-15.445zM469.333 401.622v-340.949l-277.12 158.336c-4.736 2.773-8.832 6.315-12.16 10.411-5.931 7.381-9.387 16.512-9.387 26.581v318.379zM554.667 60.672v340.949l298.667 172.757v-318.379c-0.043-5.163-1.067-10.496-2.987-15.445-3.413-8.789-9.6-16.384-18.176-21.333z" />
<glyph unicode="&#xe91b;" glyph-name="reports" d="M725.333 640h153.003l-302.336-302.336-183.168 183.168c-16.683 16.683-43.691 16.683-60.331 0l-320-320c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l289.835 289.835 183.168-183.168c16.683-16.683 43.691-16.683 60.331 0l332.501 332.501v-153.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 5.803-1.152 11.307-3.243 16.341s-5.163 9.728-9.216 13.781c-0.043 0.043-0.043 0.043-0.085 0.085-3.925 3.925-8.619 7.083-13.781 9.216-5.035 2.091-10.539 3.243-16.341 3.243h-256c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe91c;" glyph-name="shipping" d="M640 298.667h-554.667v469.333h554.667v-170.667zM725.333 554.667h110.336l102.997-102.997v-153.003h-213.333zM298.667 149.334c0-17.664-7.125-33.621-18.731-45.269s-27.605-18.731-45.269-18.731-33.621 7.125-45.269 18.731-18.731 27.605-18.731 45.269 7.125 33.621 18.731 45.269 27.605 18.731 45.269 18.731 33.621-7.125 45.269-18.731 18.731-27.605 18.731-45.269zM938.667 149.334c0 22.912-5.163 44.587-14.379 64h57.045c23.552 0 42.667 19.115 42.667 42.667v213.333c0 10.923-4.181 21.845-12.501 30.165l-128 128c-7.723 7.723-18.389 12.501-30.165 12.501h-128v170.667c0 23.552-19.115 42.667-42.667 42.667h-640c-23.552 0-42.667-19.115-42.667-42.667v-554.667c0-23.552 19.115-42.667 42.667-42.667h57.045c-9.216-19.413-14.379-41.088-14.379-64 0-41.216 16.768-78.635 43.733-105.6s64.384-43.733 105.6-43.733 78.635 16.768 105.6 43.733 43.733 64.384 43.733 105.6c0 22.912-5.163 44.587-14.379 64h284.757c-9.216-19.413-14.379-41.088-14.379-64 0-41.216 16.768-78.635 43.733-105.6s64.384-43.733 105.6-43.733 78.635 16.768 105.6 43.733 43.733 64.384 43.733 105.6zM853.333 149.334c0-17.664-7.125-33.621-18.731-45.269s-27.605-18.731-45.269-18.731-33.621 7.125-45.269 18.731-18.731 27.605-18.731 45.269 7.125 33.621 18.731 45.269 27.605 18.731 45.269 18.731 33.621-7.125 45.269-18.731 18.731-27.605 18.731-45.269z" />
</font></defs></svg> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CopyLinkButton } from 'shared/components';
import { Breadcrumbs, Divider, Header, BoardName } from './Styles';
const propTypes = {
projectName: PropTypes.string.isRequired,
};
const ProjectBoardHeader = ({ projectName }) => (
<>
<Breadcrumbs>
Projects
<Divider>/</Divider>
{projectName}
<Divider>/</Divider>
Kanban Board
</Breadcrumbs>
<Header>
<BoardName>Kanban board</BoardName>
<CopyLinkButton />
</Header>
</>
);
ProjectBoardHeader.propTypes = propTypes;
export default ProjectBoardHeader;

View File

@@ -0,0 +1,27 @@
import styled from 'styled-components';
import { color, font } from 'shared/utils/styles';
export const Tip = styled.div`
display: flex;
align-items: center;
padding-top: 8px;
color: ${color.textMedium};
${font.size(13)}
strong {
padding-right: 4px;
}
`;
export const TipLetter = styled.span`
position: relative;
top: 1px;
display: inline-block;
margin: 0 4px;
padding: 0 4px;
border-radius: 2px;
color: ${color.textDarkest};
background: ${color.backgroundMedium};
${font.bold}
${font.size(12)}
`;

View File

@@ -0,0 +1,35 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { KeyCodes } from 'shared/constants/keyCodes';
import { isFocusedElementEditable } from 'shared/utils/dom';
import { Tip, TipLetter } from './Style';
const propTypes = {
setFormOpen: PropTypes.func.isRequired,
};
const ProjectBoardIssueDetailsCommentsCreateProTip = ({ setFormOpen }) => {
useEffect(() => {
const handleKeyDown = event => {
if (!isFocusedElementEditable() && event.keyCode === KeyCodes.M) {
event.preventDefault();
setFormOpen(true);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [setFormOpen]);
return (
<Tip>
<strong>Pro tip:</strong>press<TipLetter>M</TipLetter>to comment
</Tip>
);
};
ProjectBoardIssueDetailsCommentsCreateProTip.propTypes = propTypes;
export default ProjectBoardIssueDetailsCommentsCreateProTip;

View File

@@ -20,7 +20,7 @@ export const Right = styled.div`
`; `;
export const FakeTextarea = styled.div` export const FakeTextarea = styled.div`
padding: 12px 20px; padding: 12px 16px;
border-radius: 4px; border-radius: 4px;
border: 1px solid ${color.borderLightest}; border: 1px solid ${color.borderLightest};
color: ${color.textLight}; color: ${color.textLight};
@@ -29,27 +29,3 @@ export const FakeTextarea = styled.div`
border: 1px solid ${color.borderLight}; border: 1px solid ${color.borderLight};
} }
`; `;
export const Tip = styled.div`
display: flex;
align-items: center;
padding-top: 8px;
color: ${color.textMedium};
${font.size(13)}
strong {
padding-right: 4px;
}
`;
export const TipLetter = styled.span`
position: relative;
top: 1px;
display: inline-block;
margin: 0 4px;
padding: 0 4px;
border-radius: 2px;
color: ${color.textDarkest};
background: ${color.backgroundMedium};
${font.bold}
${font.size(12)}
`;

View File

@@ -5,7 +5,8 @@ import api from 'shared/utils/api';
import useApi from 'shared/hooks/api'; import useApi from 'shared/hooks/api';
import toast from 'shared/utils/toast'; import toast from 'shared/utils/toast';
import BodyForm from '../BodyForm'; import BodyForm from '../BodyForm';
import { Create, UserAvatar, Right, FakeTextarea, Tip, TipLetter } from './Style'; import ProTip from './ProTip';
import { Create, UserAvatar, Right, FakeTextarea } from './Style';
const propTypes = { const propTypes = {
issueId: PropTypes.number.isRequired, issueId: PropTypes.number.isRequired,
@@ -32,6 +33,7 @@ const ProjectBoardIssueDetailsCommentsCreate = ({ issueId, fetchIssue }) => {
toast.error(error); toast.error(error);
} }
}; };
return ( return (
<Create> <Create>
{currentUser && <UserAvatar name={currentUser.name} avatarUrl={currentUser.avatarUrl} />} {currentUser && <UserAvatar name={currentUser.name} avatarUrl={currentUser.avatarUrl} />}
@@ -47,9 +49,7 @@ const ProjectBoardIssueDetailsCommentsCreate = ({ issueId, fetchIssue }) => {
) : ( ) : (
<> <>
<FakeTextarea onClick={() => setFormOpen(true)}>Add a comment...</FakeTextarea> <FakeTextarea onClick={() => setFormOpen(true)}>Add a comment...</FakeTextarea>
<Tip> <ProTip setFormOpen={setFormOpen} />
<strong>Pro tip:</strong>press<TipLetter>M</TipLetter>to comment
</Tip>
</> </>
)} )}
</Right> </Right>

View File

@@ -6,7 +6,7 @@ import Comment from './Comment';
import { Comments, Title } from './Styles'; import { Comments, Title } from './Styles';
const propTypes = { const propTypes = {
issue: PropTypes.array.isRequired, issue: PropTypes.object.isRequired,
fetchIssue: PropTypes.func.isRequired, fetchIssue: PropTypes.func.isRequired,
}; };

View File

@@ -0,0 +1,12 @@
import styled from 'styled-components';
import { color, font } from 'shared/utils/styles';
export const Dates = styled.div`
margin-top: 11px;
padding-top: 13px;
line-height: 22px;
border-top: 1px solid ${color.borderLightest};
color: ${color.textMedium};
${font.size(13)}
`;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { formatDateTimeConversational } from 'shared/utils/dateTime';
import { Dates } from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,
};
const ProjectBoardIssueDetailsDates = ({ issue }) => (
<Dates>
<div>Created at {formatDateTimeConversational(issue.createdAt)}</div>
<div>Updated at {formatDateTimeConversational(issue.updatedAt)}</div>
</Dates>
);
ProjectBoardIssueDetailsDates.propTypes = propTypes;
export default ProjectBoardIssueDetailsDates;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import api from 'shared/utils/api';
import toast from 'shared/utils/toast';
import { Button, ConfirmModal } from 'shared/components';
const propTypes = {
issue: PropTypes.object.isRequired,
fetchProject: PropTypes.func.isRequired,
modalClose: PropTypes.func.isRequired,
};
const ProjectBoardIssueDetailsDelete = ({ issue, fetchProject, modalClose }) => {
const handleIssueDelete = async () => {
try {
await api.delete(`/issues/${issue.id}`);
await fetchProject();
modalClose();
} catch (error) {
toast.error(error);
}
};
return (
<ConfirmModal
title="Are you sure you want to delete this issue?"
message="Once you delete, it's gone for good."
confirmText="Delete issue"
onConfirm={handleIssueDelete}
renderLink={modal => <Button icon="trash" iconSize={19} color="empty" onClick={modal.open} />}
/>
);
};
ProjectBoardIssueDetailsDelete.propTypes = propTypes;
export default ProjectBoardIssueDetailsDelete;

View File

@@ -14,9 +14,6 @@ const ProjectBoardIssueDetailsDescription = ({ issue, updateIssue }) => {
const $editorRef = useRef(); const $editorRef = useRef();
const [isPresenting, setPresenting] = useState(true); const [isPresenting, setPresenting] = useState(true);
const isDescriptionEmpty = description =>
getTextContentsFromHtmlString(description).trim().length === 0;
const renderPresentingMode = () => const renderPresentingMode = () =>
isDescriptionEmpty(issue.description) ? ( isDescriptionEmpty(issue.description) ? (
<EmptyLabel onClick={() => setPresenting(false)}>Add a description...</EmptyLabel> <EmptyLabel onClick={() => setPresenting(false)}>Add a description...</EmptyLabel>
@@ -55,6 +52,9 @@ const ProjectBoardIssueDetailsDescription = ({ issue, updateIssue }) => {
); );
}; };
const isDescriptionEmpty = description =>
getTextContentsFromHtmlString(description).trim().length === 0;
ProjectBoardIssueDetailsDescription.propTypes = propTypes; ProjectBoardIssueDetailsDescription.propTypes = propTypes;
export default ProjectBoardIssueDetailsDescription; export default ProjectBoardIssueDetailsDescription;

View File

@@ -0,0 +1,23 @@
import styled from 'styled-components';
import { font } from 'shared/utils/styles';
export const FeedbackDropdown = styled.div`
padding: 16px 24px 24px;
`;
export const FeedbackImageCont = styled.div`
padding: 24px 56px 20px;
`;
export const FeedbackImage = styled.img`
width: 100%;
`;
export const FeedbackParagraph = styled.p`
margin-bottom: 12px;
${font.size(15)}
&:last-of-type {
margin-bottom: 22px;
}
`;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Button, Tooltip } from 'shared/components';
import feedbackImage from './assets/feedback.png';
import { FeedbackDropdown, FeedbackImageCont, FeedbackImage, FeedbackParagraph } from './Styles';
const ProjectBoardIssueDetailsFeedback = () => (
<Tooltip
width={300}
offset={{ top: -15 }}
renderLink={linkProps => (
<Button icon="feedback" color="empty" {...linkProps}>
Give feedback
</Button>
)}
renderContent={() => (
<FeedbackDropdown>
<FeedbackImageCont>
<FeedbackImage src={feedbackImage} alt="Give feedback" />
</FeedbackImageCont>
<FeedbackParagraph>
This simplified Jira clone is built with React on the front-end and Node/TypeScript on the
back-end.
</FeedbackParagraph>
<FeedbackParagraph>
{'Read more on our website or reach out via '}
<a href="mailto:ivor@codetree.co">
<strong>ivor@codetree.co</strong>
</a>
</FeedbackParagraph>
<a href="https://codetree.co/" target="_blank" rel="noreferrer noopener">
<Button color="primary">Visit Website</Button>
</a>
<a href="https://github.com/oldboyxx/jira_clone" target="_blank" rel="noreferrer noopener">
<Button style={{ marginLeft: 10 }} icon="github">
Github Repo
</Button>
</a>
</FeedbackDropdown>
)}
/>
);
export default ProjectBoardIssueDetailsFeedback;

View File

@@ -0,0 +1,24 @@
import styled, { css } from 'styled-components';
import { color, font } from 'shared/utils/styles';
export const Priority = styled.div`
display: flex;
align-items: center;
${props =>
props.isValue &&
css`
padding: 3px 4px 3px 0px;
border-radius: 4px;
&:hover,
&:focus {
background: ${color.backgroundLight};
}
`}
`;
export const Label = styled.div`
text-transform: capitalize;
padding: 0 3px 0 8px;
${font.size(14.5)}
`;

View File

@@ -4,7 +4,7 @@ import { invert } from 'lodash';
import { IssuePriority } from 'shared/constants/issues'; import { IssuePriority } from 'shared/constants/issues';
import { Select, IssuePriorityIcon } from 'shared/components'; import { Select, IssuePriorityIcon } from 'shared/components';
import { Priority, Option, Label } from './Styles'; import { Priority, Label } from './Styles';
import { SectionTitle } from '../Styles'; import { SectionTitle } from '../Styles';
const IssuePriorityCopy = invert(IssuePriority); const IssuePriorityCopy = invert(IssuePriority);
@@ -15,8 +15,8 @@ const propTypes = {
}; };
const ProjectBoardIssueDetailsPriority = ({ issue, updateIssue }) => { const ProjectBoardIssueDetailsPriority = ({ issue, updateIssue }) => {
const renderPriorityItem = priority => ( const renderPriorityItem = (priority, isValue) => (
<Priority color={priority}> <Priority isValue={isValue}>
<IssuePriorityIcon priority={priority} /> <IssuePriorityIcon priority={priority} />
<Label>{IssuePriorityCopy[priority].toLowerCase()}</Label> <Label>{IssuePriorityCopy[priority].toLowerCase()}</Label>
</Priority> </Priority>
@@ -25,16 +25,15 @@ const ProjectBoardIssueDetailsPriority = ({ issue, updateIssue }) => {
<> <>
<SectionTitle>Priority</SectionTitle> <SectionTitle>Priority</SectionTitle>
<Select <Select
dropdownWidth={343}
value={issue.priority} value={issue.priority}
options={Object.values(IssuePriority).map(priority => ({ options={Object.values(IssuePriority).map(priority => ({
value: priority, value: priority,
label: IssuePriorityCopy[priority], label: IssuePriorityCopy[priority],
}))} }))}
onChange={priority => updateIssue({ priority })} onChange={priority => updateIssue({ priority })}
renderValue={({ value }) => renderPriorityItem(value)} renderValue={({ value }) => renderPriorityItem(value, true)}
renderOption={({ value, ...optionProps }) => ( renderOption={({ value }) => renderPriorityItem(value)}
<Option {...optionProps}>{renderPriorityItem(value)}</Option>
)}
/> />
</> </>
); );

View File

@@ -0,0 +1,18 @@
import styled, { css } from 'styled-components';
import { issueStatusColors, issueStatusBackgroundColors, mixin } from 'shared/utils/styles';
export const Status = styled.div`
text-transform: uppercase;
transition: all 0.1s;
${props => mixin.tag(issueStatusBackgroundColors[props.color], issueStatusColors[props.color])}
${props =>
props.isValue &&
css`
padding: 0 12px;
height: 32px;
&:hover {
transform: scale(1.05);
}
`}
`;

View File

@@ -2,8 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues'; import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues';
import { Select } from 'shared/components'; import { Select, Icon } from 'shared/components';
import { Status, Option } from './Styles'; import { Status } from './Styles';
import { SectionTitle } from '../Styles'; import { SectionTitle } from '../Styles';
const propTypes = { const propTypes = {
@@ -15,6 +15,7 @@ const ProjectBoardIssueDetailsStatus = ({ issue, updateIssue }) => (
<> <>
<SectionTitle>Status</SectionTitle> <SectionTitle>Status</SectionTitle>
<Select <Select
dropdownWidth={343}
value={issue.status} value={issue.status}
options={Object.values(IssueStatus).map(status => ({ options={Object.values(IssueStatus).map(status => ({
value: status, value: status,
@@ -22,14 +23,13 @@ const ProjectBoardIssueDetailsStatus = ({ issue, updateIssue }) => (
}))} }))}
onChange={status => updateIssue({ status })} onChange={status => updateIssue({ status })}
renderValue={({ value: status }) => ( renderValue={({ value: status }) => (
<Status isLarge color={status}> <Status isValue color={status}>
{IssueStatusCopy[status]} <div>{IssueStatusCopy[status]}</div>
<Icon type="chevron-down" size={18} />
</Status> </Status>
)} )}
renderOption={({ value: status, ...optionProps }) => ( renderOption={({ value: status }) => (
<Option {...optionProps}> <Status color={status}>{IssueStatusCopy[status]}</Status>
<Status color={status}>{IssueStatusCopy[status]}</Status>
</Option>
)} )}
/> />
</> </>

View File

@@ -17,6 +17,20 @@ export const Right = styled.div`
padding-top: 5px; padding-top: 5px;
`; `;
export const TopActions = styled.div`
display: flex;
justify-content: space-between;
padding: 21px 18px 0;
`;
export const TopActionsRight = styled.div`
display: flex;
align-items: center;
& > * {
margin-left: 4px;
}
`;
export const SectionTitle = styled.div` export const SectionTitle = styled.div`
margin: 24px 0 5px; margin: 24px 0 5px;
text-transform: uppercase; text-transform: uppercase;

View File

@@ -3,12 +3,20 @@ import styled from 'styled-components';
import { color, font, mixin } from 'shared/utils/styles'; import { color, font, mixin } from 'shared/utils/styles';
import { Icon } from 'shared/components'; import { Icon } from 'shared/components';
export const TrackingLink = styled.div`
padding: 4px 4px 2px 0;
border-radius: 4px;
transition: background 0.1s;
${mixin.clickable}
&:hover {
background: ${color.backgroundLight};
}
`;
export const Tracking = styled.div` export const Tracking = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-top: 2px;
${mixin.clickable};
`; `;
export const WatchIcon = styled(Icon)` export const WatchIcon = styled(Icon)`
@@ -22,7 +30,7 @@ export const Right = styled.div`
export const BarCont = styled.div` export const BarCont = styled.div`
height: 5px; height: 5px;
border-radius: 4px; border-radius: 4px;
background: ${color.backgroundLight}; background: ${color.backgroundMedium};
`; `;
export const Bar = styled.div` export const Bar = styled.div`
@@ -41,7 +49,7 @@ export const Values = styled.div`
`; `;
export const ModalContents = styled.div` export const ModalContents = styled.div`
padding: 20px; padding: 20px 25px 25px;
`; `;
export const ModalTitle = styled.div` export const ModalTitle = styled.div`

View File

@@ -4,6 +4,7 @@ import { isNil } from 'lodash';
import { InputDebounced, Modal, Button } from 'shared/components'; import { InputDebounced, Modal, Button } from 'shared/components';
import { import {
TrackingLink,
Tracking, Tracking,
WatchIcon, WatchIcon,
Right, Right,
@@ -31,7 +32,7 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
filter={/^\d{0,6}$/} filter={/^\d{0,6}$/}
value={isNil(issue[fieldName]) ? '' : issue[fieldName]} value={isNil(issue[fieldName]) ? '' : issue[fieldName]}
onChange={stringValue => { onChange={stringValue => {
const value = stringValue.trim() ? parseInt(stringValue) : null; const value = stringValue.trim() ? Number(stringValue) : null;
updateIssue({ [fieldName]: value }); updateIssue({ [fieldName]: value });
}} }}
/> />
@@ -95,7 +96,7 @@ const ProjectBoardIssueDetailsTracking = ({ issue, updateIssue }) => {
<SectionTitle>Time Tracking</SectionTitle> <SectionTitle>Time Tracking</SectionTitle>
<Modal <Modal
width={400} width={400}
renderLink={modal => renderTrackingPreview(modal.open)} renderLink={modal => <TrackingLink>{renderTrackingPreview(modal.open)}</TrackingLink>}
renderContent={modal => ( renderContent={modal => (
<ModalContents> <ModalContents>
<ModalTitle>Time tracking</ModalTitle> <ModalTitle>Time tracking</ModalTitle>

View File

@@ -3,12 +3,6 @@ import styled from 'styled-components';
import { color, font, mixin } from 'shared/utils/styles'; import { color, font, mixin } from 'shared/utils/styles';
import { Button } from 'shared/components'; import { Button } from 'shared/components';
export const TopActions = styled.div`
display: flex;
justify-content: space-between;
padding: 21px 18px 0;
`;
export const TypeButton = styled(Button)` export const TypeButton = styled(Button)`
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@@ -16,14 +10,6 @@ export const TypeButton = styled(Button)`
${font.size(13)} ${font.size(13)}
`; `;
export const Right = styled.div`
display: flex;
align-items: center;
& > * {
margin-left: 4px;
}
`;
export const TypeDropdown = styled.div` export const TypeDropdown = styled.div`
padding-bottom: 6px; padding-bottom: 6px;
`; `;
@@ -50,23 +36,3 @@ export const TypeLabel = styled.div`
text-transform: capitalize; text-transform: capitalize;
${font.size(15)} ${font.size(15)}
`; `;
export const FeedbackDropdown = styled.div`
padding: 16px 24px 24px;
`;
export const FeedbackImageCont = styled.div`
padding: 24px 56px 20px;
`;
export const FeedbackImage = styled.img`
width: 100%;
`;
export const FeedbackParagraph = styled.p`
margin-bottom: 12px;
${font.size(15)}
&:last-of-type {
margin-bottom: 22px;
}
`;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IssueType } from 'shared/constants/issues';
import { IssueTypeIcon, Tooltip } from 'shared/components';
import { TypeButton, TypeDropdown, TypeTitle, Type, TypeLabel } from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,
updateIssue: PropTypes.func.isRequired,
};
const ProjectBoardIssueDetailsType = ({ issue, updateIssue }) => (
<Tooltip
width={150}
offset={{ top: -15 }}
renderLink={linkProps => (
<TypeButton {...linkProps} color="empty" icon={<IssueTypeIcon type={issue.type} />}>
{`${issue.type}-${issue.id}`}
</TypeButton>
)}
renderContent={() => (
<TypeDropdown>
<TypeTitle>Change issue type</TypeTitle>
{Object.values(IssueType).map(type => (
<Type key={type} onClick={() => updateIssue({ type })}>
<IssueTypeIcon type={type} top={1} />
<TypeLabel>{type}</TypeLabel>
</Type>
))}
</TypeDropdown>
)}
/>
);
ProjectBoardIssueDetailsType.propTypes = propTypes;
export default ProjectBoardIssueDetailsType;

View File

@@ -13,6 +13,10 @@ export const User = styled.div`
padding: 4px 8px; padding: 4px 8px;
border-radius: 4px; border-radius: 4px;
background: ${color.backgroundLight}; background: ${color.backgroundLight};
transition: background 0.1s;
&:hover {
background: ${color.backgroundMedium};
}
`} `}
`; `;
@@ -20,11 +24,3 @@ export const Username = styled.div`
padding: 0 3px 0 8px; padding: 0 3px 0 8px;
${font.size(14.5)} ${font.size(14.5)}
`; `;
export const Option = styled.div`
padding: 8px 12px 5px;
${mixin.clickable}
&.jira-select-option-is-active {
background: ${color.backgroundLightPrimary};
}
`;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Avatar, Select, Icon } from 'shared/components'; import { Avatar, Select, Icon } from 'shared/components';
import { User, Username, Option } from './Styles'; import { User, Username } from './Styles';
import { SectionTitle } from '../Styles'; import { SectionTitle } from '../Styles';
const propTypes = { const propTypes = {
@@ -12,7 +12,7 @@ const propTypes = {
}; };
const ProjectBoardIssueDetailsUsers = ({ issue, updateIssue, projectUsers }) => { const ProjectBoardIssueDetailsUsers = ({ issue, updateIssue, projectUsers }) => {
const getUserById = userId => projectUsers.find(user => user.id === parseInt(userId)); const getUserById = userId => projectUsers.find(user => user.id === userId);
const userOptions = projectUsers.map(user => ({ value: user.id, label: user.name })); const userOptions = projectUsers.map(user => ({ value: user.id, label: user.name }));
@@ -41,6 +41,7 @@ const ProjectBoardIssueDetailsUsers = ({ issue, updateIssue, projectUsers }) =>
<SectionTitle>Assignees</SectionTitle> <SectionTitle>Assignees</SectionTitle>
<Select <Select
isMulti isMulti
dropdownWidth={343}
placeholder="Unassigned" placeholder="Unassigned"
value={issue.userIds} value={issue.userIds}
options={userOptions} options={userOptions}
@@ -50,9 +51,7 @@ const ProjectBoardIssueDetailsUsers = ({ issue, updateIssue, projectUsers }) =>
renderValue={({ value, removeOptionValue }) => renderValue={({ value, removeOptionValue }) =>
renderUserValue(getUserById(value), true, removeOptionValue) renderUserValue(getUserById(value), true, removeOptionValue)
} }
renderOption={({ value, ...optionProps }) => ( renderOption={({ value }) => renderUserOption(getUserById(value))}
<Option {...optionProps}>{renderUserOption(getUserById(value))}</Option>
)}
/> />
</> </>
); );
@@ -61,13 +60,12 @@ const ProjectBoardIssueDetailsUsers = ({ issue, updateIssue, projectUsers }) =>
<> <>
<SectionTitle>Reporter</SectionTitle> <SectionTitle>Reporter</SectionTitle>
<Select <Select
dropdownWidth={343}
value={issue.reporterId} value={issue.reporterId}
options={userOptions} options={userOptions}
onChange={userId => updateIssue({ reporterId: userId })} onChange={userId => updateIssue({ reporterId: userId })}
renderValue={({ value }) => renderUserValue(getUserById(value), false)} renderValue={({ value }) => renderUserValue(getUserById(value), false)}
renderOption={({ value, ...optionProps }) => ( renderOption={({ value }) => renderUserOption(getUserById(value))}
<Option {...optionProps}>{renderUserOption(getUserById(value))}</Option>
)}
/> />
</> </>
); );

View File

@@ -3,9 +3,11 @@ import PropTypes from 'prop-types';
import api from 'shared/utils/api'; import api from 'shared/utils/api';
import useApi from 'shared/hooks/api'; import useApi from 'shared/hooks/api';
import { PageError } from 'shared/components'; import { PageError, CopyLinkButton, Button } from 'shared/components';
import Loader from './Loader'; import Loader from './Loader';
import TopActions from './TopActions'; import Type from './Type';
import Feedback from './Feedback';
import Delete from './Delete';
import Title from './Title'; import Title from './Title';
import Description from './Description'; import Description from './Description';
import Comments from './Comments'; import Comments from './Comments';
@@ -13,7 +15,8 @@ import Status from './Status';
import Users from './Users'; import Users from './Users';
import Priority from './Priority'; import Priority from './Priority';
import Tracking from './Tracking'; import Tracking from './Tracking';
import { Content, Left, Right } from './Styles'; import Dates from './Dates';
import { TopActions, TopActionsRight, Content, Left, Right } from './Styles';
const propTypes = { const propTypes = {
issueId: PropTypes.string.isRequired, issueId: PropTypes.string.isRequired,
@@ -54,12 +57,15 @@ const ProjectBoardIssueDetails = ({
return ( return (
<> <>
<TopActions <TopActions>
issue={issue} <Type issue={issue} updateIssue={updateIssue} />
updateIssue={updateIssue} <TopActionsRight>
fetchProject={fetchProject} <Feedback />
modalClose={modalClose} <CopyLinkButton color="empty" />
/> <Delete issue={issue} fetchProject={fetchProject} modalClose={modalClose} />
<Button icon="close" iconSize={24} color="empty" onClick={modalClose} />
</TopActionsRight>
</TopActions>
<Content> <Content>
<Left> <Left>
<Title issue={issue} updateIssue={updateIssue} /> <Title issue={issue} updateIssue={updateIssue} />
@@ -71,6 +77,7 @@ const ProjectBoardIssueDetails = ({
<Users issue={issue} updateIssue={updateIssue} projectUsers={projectUsers} /> <Users issue={issue} updateIssue={updateIssue} projectUsers={projectUsers} />
<Priority issue={issue} updateIssue={updateIssue} /> <Priority issue={issue} updateIssue={updateIssue} />
<Tracking issue={issue} updateIssue={updateIssue} /> <Tracking issue={issue} updateIssue={updateIssue} />
<Dates issue={issue} />
</Right> </Right>
</Content> </Content>
</> </>

View File

@@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
import { Avatar } from 'shared/components'; import { Avatar } from 'shared/components';
import { color, font, mixin } from 'shared/utils/styles'; import { color, font, mixin } from 'shared/utils/styles';
export const IssueWrapper = styled(Link)` export const IssueLink = styled(Link)`
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
`; `;

View File

@@ -4,7 +4,7 @@ import { useRouteMatch } from 'react-router-dom';
import { Draggable } from 'react-beautiful-dnd'; import { Draggable } from 'react-beautiful-dnd';
import { IssueTypeIcon, IssuePriorityIcon } from 'shared/components'; import { IssueTypeIcon, IssuePriorityIcon } from 'shared/components';
import { IssueWrapper, Issue, Title, Bottom, Assignees, AssigneeAvatar } from './Styles'; import { IssueLink, Issue, Title, Bottom, Assignees, AssigneeAvatar } from './Styles';
const propTypes = { const propTypes = {
projectUsers: PropTypes.array.isRequired, projectUsers: PropTypes.array.isRequired,
@@ -21,7 +21,7 @@ const ProjectBoardListsIssue = ({ projectUsers, issue, index }) => {
return ( return (
<Draggable draggableId={issue.id.toString()} index={index}> <Draggable draggableId={issue.id.toString()} index={index}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<IssueWrapper <IssueLink
to={`${match.url}/${issue.id}`} to={`${match.url}/${issue.id}`}
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}
@@ -46,7 +46,7 @@ const ProjectBoardListsIssue = ({ projectUsers, issue, index }) => {
</Assignees> </Assignees>
</Bottom> </Bottom>
</Issue> </Issue>
</IssueWrapper> </IssueLink>
)} )}
</Draggable> </Draggable>
); );

View File

@@ -29,7 +29,7 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => {
const isSamePosition = destination.index === source.index; const isSamePosition = destination.index === source.index;
if (isSameList && isSamePosition) return; if (isSameList && isSamePosition) return;
const issueId = parseInt(draggableId); const issueId = Number(draggableId);
api.optimisticUpdate({ api.optimisticUpdate({
url: `/issues/${issueId}`, url: `/issues/${issueId}`,

View File

@@ -0,0 +1,93 @@
import styled, { css } from 'styled-components';
import { NavLink } from 'react-router-dom';
import { color, sizes, font, mixin, zIndexValues } from 'shared/utils/styles';
export const Sidebar = styled.div`
position: absolute;
z-index: ${zIndexValues.navLeft - 1};
top: 0;
left: ${sizes.appNavBarLeftWidth}px;
height: 100vh;
width: 240px;
padding: 0 16px;
background: ${color.backgroundLightest};
border-right: 1px solid ${color.borderLightest};
`;
export const ProjectInfo = styled.div`
display: flex;
padding: 24px 4px;
`;
export const ProjectTexts = styled.div`
padding: 3px 0 0 10px;
`;
export const ProjectName = styled.div`
color: ${color.textDark};
${font.size(15)};
${font.medium};
`;
export const ProjectCategory = styled.div`
color: ${color.textMedium};
${font.size(13)};
`;
export const Divider = styled.div`
margin-top: 17px;
padding-top: 18px;
border-top: 1px solid ${color.borderLight};
`;
export const LinkItem = styled(NavLink)`
position: relative;
display: flex;
padding: 8px 12px;
border-radius: 3px;
color: ${color.textDark};
${mixin.clickable}
${props =>
!props.implemented
? `cursor: not-allowed;`
: css`
&:hover {
background: ${color.backgroundLight};
}
`}
i {
margin-right: 15px;
font-size: 20px;
color: ${color.textDarkest};
}
&.active {
color: ${color.primary};
background: ${color.backgroundLight};
i {
color: ${color.primary};
}
}
`;
export const LinkText = styled.div`
padding-top: 2px;
${font.size(14.7)};
`;
export const NotImplemented = styled.div`
display: none;
position: absolute;
top: 9px;
left: 104%;
width: 120px;
padding: 3px 0 3px 8px;
border-radius: 3px;
color: #fff;
background: #000;
${font.size(12.5)};
${font.medium}
${LinkItem}:hover & {
display: inline-block;
}
`;

View File

@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon, ProjectAvatar } from 'shared/components';
import {
Sidebar,
ProjectInfo,
ProjectTexts,
ProjectName,
ProjectCategory,
Divider,
LinkItem,
LinkText,
NotImplemented,
} from './Styles';
const propTypes = {
projectName: PropTypes.string.isRequired,
matchPath: PropTypes.string.isRequired,
};
const ProjectSidebar = ({ projectName, matchPath }) => {
const renderLinkItem = (text, iconType, path = '') => (
<LinkItem exact to={`${matchPath}${path}`} implemented={path}>
<Icon type={iconType} />
<LinkText>{text}</LinkText>
{!path && <NotImplemented>Not implemented</NotImplemented>}
</LinkItem>
);
return (
<Sidebar>
<ProjectInfo>
<ProjectAvatar />
<ProjectTexts>
<ProjectName>{projectName}</ProjectName>
<ProjectCategory>Software project</ProjectCategory>
</ProjectTexts>
</ProjectInfo>
{renderLinkItem('Kanban Board', 'board', '/board')}
{renderLinkItem('Reports', 'reports')}
<Divider />
{renderLinkItem('Releases', 'shipping')}
{renderLinkItem('Issues and filters', 'issues')}
{renderLinkItem('Pages', 'page')}
{renderLinkItem('Components', 'component')}
{renderLinkItem('Project settings', 'settings')}
</Sidebar>
);
};
ProjectSidebar.propTypes = propTypes;
export default ProjectSidebar;

View File

@@ -1,41 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { copyToClipboard } from 'shared/utils/clipboard';
import { Button } from 'shared/components';
import { Breadcrumbs, Divider, Header, BoardName } from './Styles';
const propTypes = {
projectName: PropTypes.string.isRequired,
};
const ProjectBoardHeader = ({ projectName }) => {
const [isLinkCopied, setLinkCopied] = useState(false);
const handleLinkCopy = () => {
setLinkCopied(true);
setTimeout(() => setLinkCopied(false), 2000);
copyToClipboard(window.location.href);
};
return (
<>
<Breadcrumbs>
Projects
<Divider>/</Divider>
{projectName}
<Divider>/</Divider>
Kanban Board
</Breadcrumbs>
<Header>
<BoardName>Kanban board</BoardName>
<Button icon="link" onClick={handleLinkCopy}>
{isLinkCopied ? 'Link Copied' : 'Copy link'}
</Button>
</Header>
</>
);
};
ProjectBoardHeader.propTypes = propTypes;
export default ProjectBoardHeader;

View File

@@ -1,22 +0,0 @@
import styled from 'styled-components';
import { color, font, mixin } from 'shared/utils/styles';
export const Priority = styled.div`
display: flex;
align-items: center;
`;
export const Option = styled.div`
padding: 8px 12px;
${mixin.clickable}
&.jira-select-option-is-active {
background: ${color.backgroundLightPrimary};
}
`;
export const Label = styled.div`
text-transform: capitalize;
padding: 0 3px 0 8px;
${font.size(14.5)}
`;

View File

@@ -1,16 +0,0 @@
import styled from 'styled-components';
import { color, issueStatusColors, issueStatusBackgroundColors, mixin } from 'shared/utils/styles';
export const Status = styled.div`
text-transform: uppercase;
${props => mixin.tag(issueStatusBackgroundColors[props.color], issueStatusColors[props.color])}
${props => props.isLarge && `padding: 9px 14px 8px;`}
`;
export const Option = styled.div`
padding: 8px 16px;
&.jira-select-option-is-active {
background: ${color.backgroundLightPrimary};
}
`;

View File

@@ -1,118 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import api from 'shared/utils/api';
import toast from 'shared/utils/toast';
import { IssueType } from 'shared/constants/issues';
import { IssueTypeIcon, Button, CopyLinkButton, Tooltip, ConfirmModal } from 'shared/components';
import feedbackImage from './assets/feedback.png';
import {
TopActions,
TypeButton,
Right,
TypeDropdown,
TypeTitle,
Type,
TypeLabel,
FeedbackDropdown,
FeedbackImageCont,
FeedbackImage,
FeedbackParagraph,
} from './Styles';
const propTypes = {
issue: PropTypes.object.isRequired,
updateIssue: PropTypes.func.isRequired,
fetchProject: PropTypes.func.isRequired,
modalClose: PropTypes.func.isRequired,
};
const ProjectBoardIssueDetailsTopActions = ({ issue, updateIssue, fetchProject, modalClose }) => {
const handleIssueDelete = async () => {
try {
await api.delete(`/issues/${issue.id}`);
await fetchProject();
modalClose();
} catch (error) {
toast.error(error);
}
};
const renderType = () => (
<Tooltip
width={150}
offset={{ top: -15 }}
renderLink={linkProps => (
<TypeButton {...linkProps} color="empty" icon={<IssueTypeIcon type={issue.type} />}>
{`${issue.type}-${issue.id}`}
</TypeButton>
)}
renderContent={() => (
<TypeDropdown>
<TypeTitle>Change issue type</TypeTitle>
{Object.values(IssueType).map(type => (
<Type key={type} onClick={() => updateIssue({ type })}>
<IssueTypeIcon type={type} top={1} />
<TypeLabel>{type}</TypeLabel>
</Type>
))}
</TypeDropdown>
)}
/>
);
const renderFeedback = () => (
<Tooltip
width={300}
offset={{ top: -15 }}
renderLink={linkProps => (
<Button icon="feedback" color="empty" {...linkProps}>
Give feedback
</Button>
)}
renderContent={() => (
<FeedbackDropdown>
<FeedbackImageCont>
<FeedbackImage src={feedbackImage} alt="Give feedback" />
</FeedbackImageCont>
<FeedbackParagraph>
This simplified Jira clone is built with React on the front-end and Node/TypeScript on
the back-end.
</FeedbackParagraph>
<FeedbackParagraph>
Read more on our website or reach out via <strong>ivor@codetree.co</strong>
</FeedbackParagraph>
<a href="https://codetree.co/" target="_blank" rel="noreferrer noopener">
<Button color="primary">Visit Website</Button>
</a>
</FeedbackDropdown>
)}
/>
);
const renderDeleteIcon = () => (
<ConfirmModal
title="Are you sure you want to delete this issue?"
message="Once you delete, it's gone for good."
confirmText="Delete issue"
onConfirm={handleIssueDelete}
renderLink={modal => <Button icon="trash" iconSize={19} color="empty" onClick={modal.open} />}
/>
);
return (
<TopActions>
{renderType()}
<Right>
{renderFeedback()}
<CopyLinkButton color="empty" />
{renderDeleteIcon()}
<Button icon="close" iconSize={24} color="empty" onClick={modalClose} />
</Right>
</TopActions>
);
};
ProjectBoardIssueDetailsTopActions.propTypes = propTypes;
export default ProjectBoardIssueDetailsTopActions;

View File

@@ -1,56 +0,0 @@
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { color, sizes, font, mixin } from 'shared/utils/styles';
export const Sidebar = styled.div`
position: absolute;
top: 0;
left: ${sizes.appNavBarLeftWidth}px;
height: 100vh;
width: 240px;
padding: 0 16px;
background: ${color.backgroundLightest};
border-right: 1px solid ${color.borderLightest};
`;
export const ProjectInfo = styled.div`
display: flex;
padding: 24px 4px;
`;
export const ProjectTexts = styled.div`
padding: 3px 0 0 10px;
`;
export const ProjectName = styled.div`
color: ${color.textDark};
${font.size(15)};
${font.medium};
`;
export const ProjectCategory = styled.div`
color: ${color.textMedium};
${font.size(13)};
`;
export const LinkItem = styled(Link)`
display: flex;
padding: 8px 12px;
border-radius: 3px;
color: ${color.textDark};
${mixin.clickable}
&:hover {
background: ${color.backgroundLight};
}
i {
margin-right: 15px;
font-size: 20px;
color: ${color.textDarkest};
}
`;
export const LinkText = styled.div`
padding-top: 2px;
${font.size(15)};
`;

View File

@@ -1,46 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon, ProjectAvatar } from 'shared/components';
import {
Sidebar,
ProjectInfo,
ProjectTexts,
ProjectName,
ProjectCategory,
LinkItem,
LinkText,
} from './Styles';
const propTypes = {
projectName: PropTypes.string.isRequired,
matchPath: PropTypes.string.isRequired,
};
const ProjectSidebar = ({ projectName, matchPath }) => (
<Sidebar>
<ProjectInfo>
<ProjectAvatar />
<ProjectTexts>
<ProjectName>{projectName}</ProjectName>
<ProjectCategory>Software project</ProjectCategory>
</ProjectTexts>
</ProjectInfo>
<LinkItem to={`${matchPath}/board`}>
<Icon type="board" />
<LinkText>Kanban Board</LinkText>
</LinkItem>
<LinkItem to={`${matchPath}/issues`}>
<Icon type="issues" />
<LinkText>Issues and filters</LinkText>
</LinkItem>
<LinkItem to={`${matchPath}/settings`}>
<Icon type="settings" />
<LinkText>Project settings</LinkText>
</LinkItem>
</Sidebar>
);
ProjectSidebar.propTypes = propTypes;
export default ProjectSidebar;

View File

@@ -8,6 +8,7 @@ export const StyledButton = styled.button`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 32px; height: 32px;
vertical-align: middle;
line-height: 1; line-height: 1;
padding: 0 ${props => (props.iconOnly ? 9 : 12)}px; padding: 0 ${props => (props.iconOnly ? 9 : 12)}px;
white-space: nowrap; white-space: nowrap;

View File

@@ -35,8 +35,9 @@ export const StyledInput = styled(Input)`
export const Actions = styled.div` export const Actions = styled.div`
display: flex; display: flex;
padding-top: 6px;
`; `;
export const StyledButton = styled(Button)` export const StyledButton = styled(Button)`
margin: 6px 20px 0 0; margin-right: 10px;
`; `;

View File

@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { copyToClipboard } from 'shared/utils/clipboard'; import { copyToClipboard } from 'shared/utils/clipboard';
import { Button } from 'shared/components'; import { Button } from 'shared/components';
const CopyLinkButton = ({ ...otherProps }) => { const CopyLinkButton = ({ ...buttonProps }) => {
const [isLinkCopied, setLinkCopied] = useState(false); const [isLinkCopied, setLinkCopied] = useState(false);
const handleLinkCopy = () => { const handleLinkCopy = () => {
@@ -12,7 +12,7 @@ const CopyLinkButton = ({ ...otherProps }) => {
copyToClipboard(window.location.href); copyToClipboard(window.location.href);
}; };
return ( return (
<Button icon="link" onClick={handleLinkCopy} {...otherProps}> <Button icon="link" onClick={handleLinkCopy} {...buttonProps}>
{isLinkCopied ? 'Link Copied' : 'Copy link'} {isLinkCopied ? 'Link Copied' : 'Copy link'}
</Button> </Button>
); );

View File

@@ -31,7 +31,7 @@ const DatePickerDateSection = ({ withTime, value, onChange, setDropdownOpen }) =
const [selectedMonth, setSelectedMonth] = useState(moment(value || undefined).startOf('month')); const [selectedMonth, setSelectedMonth] = useState(moment(value || undefined).startOf('month'));
const handleYearChange = year => { const handleYearChange = year => {
setSelectedMonth(moment(selectedMonth).set({ year: parseInt(year) })); setSelectedMonth(moment(selectedMonth).set({ year: Number(year) }));
}; };
const handleMonthChange = addOrSubtract => { const handleMonthChange = addOrSubtract => {

View File

@@ -39,8 +39,8 @@ const DatePickerTimeSection = ({ value, onChange, setDropdownOpen }) => {
const existingDate = moment(value || undefined); const existingDate = moment(value || undefined);
const existingDateWithNewTime = existingDate.set({ const existingDateWithNewTime = existingDate.set({
hour: parseInt(newHour), hour: Number(newHour),
minute: parseInt(newMinute), minute: Number(newMinute),
}); });
onChange(formatDateTimeForAPI(existingDateWithNewTime)); onChange(formatDateTimeForAPI(existingDateWithNewTime));
setDropdownOpen(false); setDropdownOpen(false);

View File

@@ -28,6 +28,11 @@ const codes = {
[`close`]: '\\e913', [`close`]: '\\e913',
[`feedback`]: '\\e918', [`feedback`]: '\\e918',
[`trash`]: '\\e912', [`trash`]: '\\e912',
[`github`]: '\\e915',
[`shipping`]: '\\e91c',
[`component`]: '\\e91a',
[`reports`]: '\\e91b',
[`page`]: '\\e919',
}; };
const propTypes = { const propTypes = {

View File

@@ -14,8 +14,12 @@ export default styled.div`
border-radius: 3px; border-radius: 3px;
border: 1px solid ${color.borderLightest}; border: 1px solid ${color.borderLightest};
background: ${color.backgroundLightest}; background: ${color.backgroundLightest};
transition: background 0.1s;
${font.regular} ${font.regular}
${font.size(15)} ${font.size(15)}
&:hover {
background: ${color.backgroundLight};
}
&:focus { &:focus {
background: #fff; background: #fff;
border: 1px solid ${color.borderInputFocus}; border: 1px solid ${color.borderInputFocus};

View File

@@ -9,7 +9,7 @@ const propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
}; };
const InputDebounced = ({ onChange, value: propsValue, ...props }) => { const InputDebounced = ({ onChange, value: propsValue, ...inputProps }) => {
const [value, setValue] = useState(propsValue); const [value, setValue] = useState(propsValue);
const handleChange = useCallback( const handleChange = useCallback(
@@ -28,7 +28,7 @@ const InputDebounced = ({ onChange, value: propsValue, ...props }) => {
return ( return (
<Input <Input
{...props} {...inputProps}
value={value} value={value}
onChange={newValue => { onChange={newValue => {
setValue(newValue); setValue(newValue);

View File

@@ -9,7 +9,7 @@ const propTypes = {
}; };
const IssuePriorityIcon = ({ priority, ...otherProps }) => { const IssuePriorityIcon = ({ priority, ...otherProps }) => {
const iconType = [IssuePriority.LOW || IssuePriority.LOWEST].includes(priority) const iconType = [IssuePriority.LOW, IssuePriority.LOWEST].includes(priority)
? 'arrow-down' ? 'arrow-down'
: 'arrow-up'; : 'arrow-up';

View File

@@ -15,7 +15,7 @@ export const ScrollOverlay = styled.div`
export const ClickableOverlay = styled.div` export const ClickableOverlay = styled.div`
min-height: 100%; min-height: 100%;
background: ${mixin.rgba(color.textLight, 0.7)}; background: rgba(9, 30, 66, 0.54);
${props => clickOverlayStyles[props.variant]} ${props => clickOverlayStyles[props.variant]}
`; `;

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