Compare commits

...
Author SHA1 Message Date
Vamsi KuramaandGitHub 11faf3f810 Merge pull request #1600 from makeplane/stage-release
promote: stage-release to master
2023-07-20 19:36:43 +05:30
Aaryan KhandelwalandGitHub 30ea1adf61 Merge pull request #1597 from makeplane/develop
promote: develop to stage-release
2023-07-20 19:32:04 +05:30
Dakshesh JainandGitHub 546aa40aa3 fix: un-read count not mutating (#1598)
* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint

* fix: un-read count not mutating
2023-07-20 19:15:13 +05:30
NikhilandGitHub 78669363b1 fix: restrict notifications for created by when the actor is same (#1596) 2023-07-20 18:53:02 +05:30
guru_sainathandGitHub bca749986a Merge pull request #1595 from makeplane/develop
promote: develop to stage-release
2023-07-20 18:11:18 +05:30
gurusainath 229938114d Merge branch 'develop' of gurusainath:makeplane/plane into develop 2023-07-20 17:25:31 +05:30
gurusainath c5e418ab47 Merge branch 'stage-release' of gurusainath:makeplane/plane into develop 2023-07-20 17:25:00 +05:30
ecdd1f1d03 promote: develop to stage-release (#1594)
* fix: onboarding invitations overflow (#1575)

* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color

* fix: no 'Create by me' label coming up (#1573)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* style: tooltip on notification header actions (#1577)

* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: user migrations for back population (#1578)

* fix: total notifications count (#1579)

* fix: notification card (#1583)

* feat: add new icons package (#1586)

* feat: add material icons package

* chore: replace issue view icons

* chore: notification ordering (#1584)

* fix: uuid error when cycle and module updates (#1585)

* refactor: height of popover & api fetch call (#1587)

* fix: snooze dropdown overflow (#1588)

* fix: notification subscribe endpoint (#1593)

* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint

* chore: notification empty state overflow (#1592)

* chore: notification empty state overflow

* fix: white logo

* fix: custom theme default values

* fix: custom theme default values

* fix: issues count to remove archived issues (#1591)

* dev: background migration for user custom themes (#1590)

---------

Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-07-20 17:24:17 +05:30
NikhilandGitHub e687cd6f6a dev: background migration for user custom themes (#1590) 2023-07-20 16:59:09 +05:30
NikhilandGitHub 9b6721790f fix: issues count to remove archived issues (#1591) 2023-07-20 16:58:34 +05:30
gurusainath 05df65577a Merge branch 'stage-release' of gurusainath:makeplane/plane into develop 2023-07-20 16:54:40 +05:30
Aaryan KhandelwalandGitHub 52d21b9dda chore: notification empty state overflow (#1592)
* chore: notification empty state overflow

* fix: white logo

* fix: custom theme default values

* fix: custom theme default values
2023-07-20 16:51:00 +05:30
Dakshesh JainandGitHub 51f10d5f36 fix: notification subscribe endpoint (#1593)
* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint
2023-07-20 16:35:18 +05:30
9275e6f373 promote: develop to stage-release (#1589)
* fix: onboarding invitations overflow (#1575)

* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color

* fix: no 'Create by me' label coming up (#1573)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* style: tooltip on notification header actions (#1577)

* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: user migrations for back population (#1578)

* fix: total notifications count (#1579)

* fix: notification card (#1583)

* feat: add new icons package (#1586)

* feat: add material icons package

* chore: replace issue view icons

* chore: notification ordering (#1584)

* fix: uuid error when cycle and module updates (#1585)

* refactor: height of popover & api fetch call (#1587)

* fix: snooze dropdown overflow (#1588)

---------

Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
2023-07-20 15:14:57 +05:30
Aaryan KhandelwalandGitHub 4aef8c2242 fix: snooze dropdown overflow (#1588) 2023-07-20 14:49:30 +05:30
Dakshesh JainandGitHub 780573dadd refactor: height of popover & api fetch call (#1587) 2023-07-20 14:33:24 +05:30
NikhilandGitHub 5e625ab132 fix: uuid error when cycle and module updates (#1585) 2023-07-20 14:18:28 +05:30
NikhilandGitHub 34123681cf chore: notification ordering (#1584) 2023-07-20 14:18:06 +05:30
Aaryan KhandelwalandGitHub c72ff782ac feat: add new icons package (#1586)
* feat: add material icons package

* chore: replace issue view icons
2023-07-20 14:17:21 +05:30
Aaryan KhandelwalandGitHub 6eb72507a5 fix: notification card (#1583) 2023-07-20 10:52:52 +05:30
Aaryan KhandelwalandGitHub 26b18b431b fix: total notifications count (#1579) 2023-07-19 20:37:40 +05:30
NikhilandGitHub 1bae9289f5 fix: user migrations for back population (#1578) 2023-07-19 20:06:12 +05:30
5c5bcb33e3 style: tooltip on notification header actions (#1577)
* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 18:42:32 +05:30
bed5f76082 fix: no 'Create by me' label coming up (#1573)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 17:40:40 +05:30
Aaryan KhandelwalandGitHub 124c2f772e fix: onboarding invitations overflow (#1575)
* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color
2023-07-19 17:09:53 +05:30
Vihar KuramaandGitHub b38898753f Merge pull request #1570 from makeplane/develop
promote: develop to stage-release
2023-07-19 16:07:43 +05:30
guru_sainathandGitHub 86a120f11e Merge branch 'stage-release' into develop 2023-07-19 16:05:13 +05:30
Rhea JainandGitHub da603dc3f8 Update README.md (#1543) 2023-07-19 15:28:01 +05:30
NikhilandGitHub 2f3970f641 feat: beat worker for configuration (#1571) 2023-07-19 15:25:33 +05:30
NikhilandGitHub d759438ebd fix: issue automation (#1569) 2023-07-19 15:08:41 +05:30
NikhilandGitHub 0102f1d693 fix: psycopg errors (#1568) 2023-07-19 15:07:54 +05:30
53e443d816 feat: notifications (#1566)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 14:44:04 +05:30
NikhilandGitHub 98b9957753 fix: issue created notification (#1553)
* fix: issue created notification

* chore: my issues assigned notifications

* dev: fix read at notification
2023-07-19 14:31:45 +05:30
NikhilandGitHub 509af4662d dev: migrations (#1561) 2023-07-19 14:31:07 +05:30
Aaryan KhandelwalandGitHub d1f2a819f5 fix: update border colors on theme switch (#1565) 2023-07-19 13:53:22 +05:30
Aaryan KhandelwalandGitHub a42bff675b fix: archived issue details page overflow, restore button loading (#1564) 2023-07-19 13:29:08 +05:30
Aaryan KhandelwalandGitHub c71a2137e6 fix: scope and demand line graph y axis values (#1563) 2023-07-19 13:13:23 +05:30
Rhea JainandGitHub 5e1f0a2604 fixes as per consistency (#1562) 2023-07-19 13:00:11 +05:30
Aaryan KhandelwalandGitHub ccab382d7d chore: add dashboard navbar (#1560) 2023-07-19 12:16:39 +05:30
Aaryan KhandelwalandGitHub a7aa78a349 chore: high contrast theming (#1559)
* fix: archived issues empty state

* chore: update high contrast theme colors
2023-07-19 11:47:41 +05:30
Aaryan KhandelwalandGitHub 5ae963c451 fix: tour sidebar icons, auth pages scroll (#1555)
* fix: tour sidebar icons, menu button word wrap

* chore: change theme on sign out

* fix: auth pages scroll y-padding
2023-07-18 19:02:33 +05:30
Dakshesh JainandGitHub 07c097c9ad style: different style for archive & project issues (#1552)
* fix: same empty state coming for all issue list related UI

* style: different style for archive & project  issues
2023-07-18 18:58:36 +05:30
Aaryan KhandelwalandGitHub fc92d7d1a0 chore: hide issue properties if there are none (#1554) 2023-07-18 18:47:28 +05:30
NikhilandGitHub 9ba8f5c21f fix: cycle module notifications (#1536)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: improve issue fetch
2023-07-18 15:59:58 +05:30
Aaryan KhandelwalandGitHub 059b8c793a refactor: parent issue select (#1546)
* refactor: parent issue select

* fix: sibling issues list
2023-07-18 15:36:03 +05:30
Dakshesh JainandGitHub 93da220c4a fix: same empty state coming for all issue list related UI (#1547) 2023-07-18 15:28:12 +05:30
Aaryan KhandelwalandGitHub 0feab162ff style: onboarding screens (#1539)
* fix: onboarding screen styling

* chore: minor styling fixes

* chore: disable buttons if form is invalid
2023-07-18 15:20:05 +05:30
NikhilandGitHub 55a1291b1d fix: inbox issue archival (#1538)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: fix inboc issue archival

* dev: update the filter for closing issues
2023-07-18 15:10:20 +05:30
NikhilandGitHub b12a00cf4a chore: default project state colors (#1540)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* chore: project create default state colors
2023-07-18 15:10:01 +05:30
NikhilandGitHub 52ee8c5615 chore: remove logger from gpt assistant (#1542)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* chore: remove logger from gpt assistant
2023-07-18 15:09:44 +05:30
NikhilandGitHub 68108c9fe9 fix: favorite permissions (#1549)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: fix permissions for cycle and views
2023-07-18 15:09:18 +05:30
Aaryan KhandelwalandGitHub 88fbe85087 fix: button and input heights (#1548) 2023-07-18 15:00:06 +05:30
Dakshesh JainandGitHub 9b423cea4b fix: create workspace valid (#1535)
show alert on-change rather then on-submit
2023-07-18 12:08:22 +05:30
Dakshesh JainandGitHub 9d891ecce1 fix: selecting label once create using modal (#1537) 2023-07-18 12:08:09 +05:30
16a7bd3bda feat: user issue notifications (#1523)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-07-18 12:07:55 +05:30
Dakshesh JainandGitHub 6e9f3971a5 style: empty state for archive issue (#1541) 2023-07-18 12:07:35 +05:30
Aaryan KhandelwalandGitHub dddfeb17b7 refactor: dropdowns (#1532) 2023-07-17 17:35:47 +05:30
Aaryan KhandelwalandGitHub 538d67dbd9 fix: theming colors (#1533)
* chore: update border colors

* chore: loading screens bg color, custom theming default values

* chore: remove unnecessary images

* chore: update static colors

* chore: update old variable names

* chore: update issue activity icon colors

* chore: update user activity icon colors
2023-07-17 16:28:23 +05:30
NikhilandGitHub 090870b03e fix: created_by notifications (#1534)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: remove unused imports and improve the filtering

* dev: fix unread filter

* dev: fix created_by none when updating the field
2023-07-17 16:09:30 +05:30
Aaryan KhandelwalandGitHub 0a56a30ab2 fix: issue sidebar create label form (#1527)
* fix: issue sidebar create label form

* fix: issue details page overflow
2023-07-17 15:37:50 +05:30
Aaryan KhandelwalandGitHub 8df1648329 fix: dashboard pie chart legends overflow (#1528) 2023-07-17 14:48:43 +05:30
NikhilandGitHub b69c4b6b30 chore: notifications (#1515)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: triggered_by details

* dev: add bot filtering

* dev: unread notification count endpoint

* dev: update endpoint to send count for all notification types
2023-07-17 13:17:48 +05:30
NikhilandGitHub e0181342c0 fix: n+1s (#1514)
* dev: fix workspace n+1

* dev: trim down label details for issue state serializer
2023-07-17 13:17:34 +05:30
NikhilandGitHub f9f8b5c3d9 chore: update bridge id to cycle and module id (#1513)
* chore: update bridge id to cycle and module id

* fix: typo
2023-07-17 13:17:11 +05:30
Aaryan KhandelwalandGitHub 5fadf53580 style: auth screens (#1531)
* chore: invitations empty state

* style: new auth screens

* chore: update typography

* chore: update border colors

* chore: resend code text
2023-07-17 13:00:44 +05:30
Aaryan KhandelwalandGitHub da6ecd439c refactor: favorite projects fetch function, chore: update tooltip design (#1526)
* chore: update tooltip design

* fix: due date popup overflow

* fix: build error

* fix: build error

* chore: add key to map return value

* refactor: favorite projects sidebar list
2023-07-14 15:08:07 +05:30
pablohashescobarandGitHub 7914bcf486 refactor: serializers to avoid getting child items (#1504)
* refactor: serializers to avoid getting child items

* dev: update issue state serializer to remove n+1 items
2023-07-14 12:01:38 +05:30
Aaryan KhandelwalandGitHub c9a5893c3f chore: update favorite projects endpoint (#1522)
* chore: upate favorite projects list endpoint

* chore: add project button on the sidebar

* refactor: sidebar favorite projects workflow
2023-07-13 19:44:53 +05:30
Anmol Singh BhatiaandGitHub 7361657660 fix: issue delete redirection, chore: code refactor (#1521) 2023-07-13 19:00:43 +05:30
60e96bcb72 style: app sidebar revamp (#1120)
* style: app sidebar, sidebar workspace dropdown and help section styling

* style: consistent  padding and spacing

* feat: material icon global component

* style: icons updated and tooltip added

* style: project list spacing and project name truncate

* style: sidebar padding and theming

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-13 18:35:43 +05:30
Anmol Singh BhatiaandGitHub 3f3fb373cc fix: issue archive improvement, chore: code refactor (#1520)
* style: select month modal

* chore: automation setting dropdown updated ,style: month dropdown styling

* chore: restore issue alert message updated

* chore: archive issue fetching updated and code refactor

* fix: build fix
2023-07-13 17:04:24 +05:30
a829e6fc40 chore: added is_favorite filter (#1518)
* fix: notification filtering

* dev: reverse logic for archive filtering

* dev: fix watching notification

* dev: read filter

* dev: update automatic issue archival and close to send notifications

* dev: update archival structure for auto close issues

* chore: added is_favorite filter

* fix: removed the unwanted filter

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-07-13 16:51:42 +05:30
Aaryan KhandelwalandGitHub 864e592bc5 fix: onboarding screen design issues (#1517)
* fix: projects empty state flicker

* fix: tour images padding

* chore: add greeting icon on the dashboard

* chore: update email id font weight

* fix: placeholder color
2023-07-13 16:51:08 +05:30
pablohashescobarandGitHub 411a661abd fix: magic code casing (#1519) 2023-07-13 16:49:37 +05:30
pablohashescobarandGitHub 61ad6b9e0e chore: upgrade psycopg to 3 (#1507) 2023-07-13 13:51:02 +05:30
2ff49c93bd fix: notifications (#1498)
* fix: notification filtering

* dev: reverse logic for archive filtering

* dev: fix watching notification

* dev: read filter

* dev: update automatic issue archival and close to send notifications

* dev: update archival structure for auto close issues

* Closing of dialog when we click around the dialog (#1364)

* style: new empty states (#1497)

* fix: custom colors opacity

* chore: update text colors for dark mode

* fix: dropdown text colors, datepicker bg color

* chore: update text colors

* chore: updated primary bg color

* style: new empty states added

* refactor: empty state for issues

* style: empty state for estimates

* chore: update labels, estimates and integrations empty states

* fix: custom analytics sidebar

* fix: archival spelling mistake

* fix: updated the default state logic

* dev: fix key

---------

Co-authored-by: Khrystyna Derenivska <56432889+kblueberry@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-07-13 13:47:24 +05:30
Aaryan KhandelwalandGitHub 120d06983d style: inbox (#1508) 2023-07-13 11:38:45 +05:30
Anmol Singh BhatiaandGitHub c9cbca5ec8 feat: auto-archive and auto-close (#1502)
* chore: issue archive services and types added

* chore: project type and constant updated

* feat: auto-close and auto-archive feature added

* feat: implement rendering of archived issues

* feat: implemented rendering of only list view for archived issues , chore: update types and services

* feat: implemented archive issue detail page and unarchive issue functionality , chore: refactor code

* feat: activity for issue archive and issue restore added

* fix: redirection and delete fix

* fix: merge conflict

* fix: restore issue redirection fix

* fix: disable modification of issue properties for archived issues, style: disable properties styling

* fix: hide empty group, switch to list view on redirct to archived issues

* fix: remove unnecessary header buttons for archived issue

* fix: auto-close dropdown fix
2023-07-13 11:34:37 +05:30
pablohashescobarandGitHub 275942a246 feat: flag for onboarding tour completion (#1499)
* feat: flag for onboarding tour completion

* dev: boolean field

* dev: user tour completed endpoint

* dev: onboarding step json
2023-07-12 22:10:07 +05:30
Aaryan KhandelwalandGitHub a1b09fcbc6 style: onboarding screens (#1412)
* style: new onboarding screens

* chore: onboarding tour screens

* fix: build error

* fix: build errors

* style: default layout background

* chor: update user auth hook logic, style: new onboarding screens

* fix: component structure

* chore: tab responsiveness added

* fix: redirection logic

* style: welcome screens responsiveness

* chore: update workspace url input field

* style: mobile responsiveness added

* chore: complete onboarding workflow

* style: create workspace page design update

* style: workspace invitations page design update

* chore: update steps logic

* fix: step change logic

* style: tour steps
2023-07-12 19:55:08 +05:30
Aaryan KhandelwalandGitHub 26f0e9da00 fix: add missing argument (#1506) 2023-07-12 17:55:58 +05:30
pablohashescobarandGitHub fd3f6ab7a4 Merge pull request #1478 from makeplane/stage-release
promote: stage-release to master
2023-07-05 21:24:39 +05:30
pablohashescobarandGitHub d05d814efe chore: workspace invitation for self-hosted versions (#1477) 2023-07-05 21:21:31 +05:30
Vamsi KuramaandGitHub 6394f517a2 Merge pull request #1413 from makeplane/stage-release
promote: stage-release to master
2023-06-28 19:19:59 +05:30
379d258375 fix: charts design and mutation (#1426) (#1427)
* fix: pie chart overlap issue

* fix: burndown chart mutation

* fix: burndown chart mutation

Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
2023-06-28 19:09:44 +05:30
pablohashescobarandGitHub 6a27827c16 Merge pull request #1425 from makeplane/develop
promote: develop to stage-release
2023-06-28 17:48:53 +05:30
pablohashescobarandGitHub 206ca98307 Merge pull request #1418 from makeplane/develop
promote: develop to stage-release
2023-06-28 11:14:41 +05:30
pablohashescobarandGitHub 473c32d3d4 fix: github importer issue (#1414) (#1415) 2023-06-27 22:56:06 +05:30
guru_sainathandGitHub 124d1c30aa Merge pull request #1410 from makeplane/develop
promote: develop to stage-release
2023-06-27 19:01:26 +05:30
guru_sainathandGitHub fd7274ba1c Merge pull request #1406 from makeplane/develop
promote: develop to stage-release
2023-06-27 16:48:34 +05:30
pablohashescobarandGitHub 46d93525be Merge pull request #1386 from makeplane/develop
promote: develop to stage-release
2023-06-24 01:34:34 +05:30
Vamsi KuramaandGitHub dbbce439ff Merge pull request #1311 from makeplane/stage-release
promote: staging to master
2023-06-16 21:34:03 +05:30
pablohashescobarandGitHub 4c0857233e fix: workspace member invite to avoid lower permission user to invite higher permission member (#1309) 2023-06-16 19:52:24 +05:30
350 changed files with 10903 additions and 4574 deletions
+8 -8
View File
@@ -19,14 +19,14 @@
<p>
<a href="https://app.plane.so/#gh-light-mode-only" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Screen.png?updatedAt=1684942001069"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Screen.png"
alt="Plane Screens"
width="100%"
/>
</a>
<a href="https://app.plane.so/#gh-dark-mode-only" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Screens_Dark_Mode.png?updatedAt=1684942388044"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Screens+Dark+Mode.png"
alt="Plane Screens"
width="100%"
/>
@@ -86,7 +86,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Views_Dark_Mode.png?updatedAt=1684943050275"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Views+Dark+Mode.png"
alt="Plane Views"
width="100%"
/>
@@ -95,7 +95,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Issue_Detail_Dark_Mode.png?updatedAt=1684943050202"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Issue+Detail+Dark+Mode.png"
alt="Plane Issue Details"
width="100%"
/>
@@ -104,7 +104,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Cycles___Modules_Dark_Mode.png?updatedAt=1684943050281"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Cycles+%26+Modules+Dark+Mode.png"
alt="Plane Cycles and Modules"
width="100%"
/>
@@ -113,7 +113,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Analytics_Dark_Mode.png?updatedAt=1684944596824"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Analytics+Dark+Mode.png"
alt="Plane Analytics"
width="100%"
/>
@@ -122,7 +122,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Pages_Dark_Mode.png?updatedAt=1684943050202"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Pages+Dark+Mode.png"
alt="Plane Pages"
width="100%"
/>
@@ -132,7 +132,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Commad_K_Dark_Mode.png?updatedAt=1684943050312"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/Plane+Commad+K+Dark+Mode.png"
alt="Plane Command Menu"
width="100%"
/>
+1 -1
View File
@@ -49,7 +49,7 @@ USER root
RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chmod -R 777 /code
USER captain
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
python manage.py wait_for_db
celery -A plane beat -l info
+1 -1
View File
@@ -6,4 +6,4 @@ python manage.py migrate
# Create a Default User
python bin/user_script.py
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
+4 -4
View File
@@ -462,9 +462,9 @@ class IssueAttachmentSerializer(BaseSerializer):
# Issue Serializer with state details
class IssueStateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state")
project_detail = ProjectSerializer(read_only=True, source="project")
label_details = LabelSerializer(read_only=True, source="labels", many=True)
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
bridge_id = serializers.UUIDField(read_only=True)
@@ -477,7 +477,7 @@ class IssueStateSerializer(BaseSerializer):
class IssueSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
parent_detail = IssueFlatSerializer(read_only=True, source="parent")
label_details = LabelSerializer(read_only=True, source="labels", many=True)
+2 -2
View File
@@ -106,7 +106,7 @@ class ModuleFlatSerializer(BaseSerializer):
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = IssueStateSerializer(read_only=True, source="issue")
issue_detail = ProjectLiteSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
@@ -151,7 +151,7 @@ class ModuleLinkSerializer(BaseSerializer):
class ModuleSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
lead_detail = UserLiteSerializer(read_only=True, source="lead")
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
link_module = ModuleLinkSerializer(read_only=True, many=True)
@@ -1,8 +1,10 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from plane.db.models import Notification
class NotificationSerializer(BaseSerializer):
triggered_by_details = UserLiteSerializer(read_only=True, source="triggered_by")
class Meta:
model = Notification
+3 -8
View File
@@ -110,8 +110,8 @@ class ProjectMemberSerializer(BaseSerializer):
class ProjectMemberInviteSerializer(BaseSerializer):
project = ProjectSerializer(read_only=True)
workspace = WorkSpaceSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = ProjectMemberInvite
@@ -125,7 +125,7 @@ class ProjectIdentifierSerializer(BaseSerializer):
class ProjectFavoriteSerializer(BaseSerializer):
project_detail = ProjectSerializer(source="project", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = ProjectFavorite
@@ -136,11 +136,6 @@ class ProjectFavoriteSerializer(BaseSerializer):
]
class ProjectLiteSerializer(BaseSerializer):
class Meta:
model = Project
fields = ["id", "identifier", "name"]
read_only_fields = fields
class ProjectMemberLiteSerializer(BaseSerializer):
+13 -2
View File
@@ -22,6 +22,7 @@ from plane.api.views import (
# User
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
## End User
# Workspaces
@@ -152,6 +153,7 @@ from plane.api.views import (
## End Analytics
# Notification
NotificationViewSet,
UnreadNotificationEndpoint,
## End Notification
)
@@ -202,7 +204,12 @@ urlpatterns = [
path(
"users/me/onboard/",
UpdateUserOnBoardedEndpoint.as_view(),
name="change-password",
name="user-onboard",
),
path(
"users/me/tour-completed/",
UpdateUserTourCompletedEndpoint.as_view(),
name="user-tour",
),
path("users/activities/", UserActivityEndpoint.as_view(), name="user-activities"),
# user workspaces
@@ -472,7 +479,6 @@ urlpatterns = [
"workspaces/<str:slug>/user-favorite-projects/",
ProjectFavoritesViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
@@ -1377,5 +1383,10 @@ urlpatterns = [
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/unread/",
UnreadNotificationEndpoint.as_view(),
name="unread-notifications",
),
## End Notification
]
+2 -1
View File
@@ -16,6 +16,7 @@ from .project import (
from .people import (
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
)
@@ -144,4 +145,4 @@ from .analytic import (
DefaultAnalyticsEndpoint,
)
from .notification import NotificationViewSet
from .notification import NotificationViewSet, UnreadNotificationEndpoint
+1 -1
View File
@@ -345,7 +345,7 @@ class MagicSignInEndpoint(BaseAPIView):
def post(self, request):
try:
user_token = request.data.get("token", "").strip().lower()
user_token = request.data.get("token", "").strip()
key = request.data.get("key", False)
if not key or user_token == "":
-3
View File
@@ -706,9 +706,6 @@ class CycleDateCheckEndpoint(BaseAPIView):
class CycleFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = CycleFavoriteSerializer
model = CycleFavorite
-26
View File
@@ -30,31 +30,6 @@ class GPTIntegrationEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST,
)
count = 0
# If logger is enabled check for request limit
if settings.LOGGER_BASE_URL:
try:
headers = {
"Content-Type": "application/json",
}
response = requests.post(
settings.LOGGER_BASE_URL,
json={"user_id": str(request.user.id)},
headers=headers,
)
count = response.json().get("count", 0)
if not response.json().get("success", False):
return Response(
{
"error": "You have surpassed the monthly limit for AI assistance"
},
status=status.HTTP_429_TOO_MANY_REQUESTS,
)
except Exception as e:
capture_exception(e)
prompt = request.data.get("prompt", False)
task = request.data.get("task", False)
@@ -82,7 +57,6 @@ class GPTIntegrationEndpoint(BaseAPIView):
{
"response": text,
"response_html": text_html,
"count": count,
"project_detail": ProjectLiteSerializer(project).data,
"workspace_detail": WorkspaceLiteSerializer(workspace).data,
},
+5 -6
View File
@@ -45,7 +45,6 @@ from plane.api.serializers import (
IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer,
ProjectMemberSerializer,
ProjectMemberLiteSerializer,
)
from plane.api.permissions import (
@@ -169,8 +168,8 @@ class IssueViewSet(BaseViewSet):
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__id"))
.annotate(module_id=F("issue_module__id"))
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -955,8 +954,8 @@ class IssueArchiveViewSet(BaseViewSet):
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__id"))
.annotate(module_id=F("issue_module__id"))
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -1087,7 +1086,7 @@ class IssueArchiveViewSet(BaseViewSet):
issue.save()
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_in": None}),
requested_data=json.dumps({"archived_at": None}),
actor_id=str(request.user.id),
issue_id=str(issue.id),
project_id=str(project_id),
-3
View File
@@ -480,9 +480,6 @@ class ModuleLinkViewSet(BaseViewSet):
class ModuleFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite
+63 -10
View File
@@ -8,7 +8,7 @@ from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet
from .base import BaseViewSet, BaseAPIView
from plane.db.models import Notification, IssueAssignee, IssueSubscriber, Issue
from plane.api.serializers import NotificationSerializer
@@ -25,21 +25,21 @@ class NotificationViewSet(BaseViewSet):
workspace__slug=self.kwargs.get("slug"),
receiver_id=self.request.user.id,
)
.select_related("workspace")
.select_related("workspace", "project," "triggered_by", "receiver")
)
def list(self, request, slug):
try:
order_by = request.GET.get("order_by", "-created_at")
snoozed = request.GET.get("snoozed", "false")
archived = request.GET.get("archived", "false")
read = request.GET.get("read", "true")
# Filter type
type = request.GET.get("type", "all")
notifications = Notification.objects.filter(
workspace__slug=slug, receiver_id=request.user.id
).order_by(order_by)
).order_by("snoozed_till", "-created_at")
# Filter for snoozed notifications
if snoozed == "false":
@@ -49,20 +49,23 @@ class NotificationViewSet(BaseViewSet):
if snoozed == "true":
notifications = notifications.filter(
snoozed_till__lt=timezone.now(),
Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False)
)
if read == "false":
notifications = notifications.filter(read_at__isnull=True)
# Filter for archived or unarchive
if archived == "true":
if archived == "false":
notifications = notifications.filter(archived_at__isnull=True)
if archived == "false":
if archived == "true":
notifications = notifications.filter(archived_at__isnull=False)
# Subscribed issues
if type == "watching":
issue_ids = IssueSubscriber.objects.filter(
workspace__slug=slug, subsriber_id=request.user.id
workspace__slug=slug, subscriber_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
@@ -117,7 +120,7 @@ class NotificationViewSet(BaseViewSet):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def mark_read(self, request, slug, pk):
try:
notification = Notification.objects.get(
@@ -160,7 +163,6 @@ class NotificationViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
def archive(self, request, slug, pk):
try:
notification = Notification.objects.get(
@@ -203,3 +205,54 @@ class NotificationViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
class UnreadNotificationEndpoint(BaseAPIView):
def get(self, request, slug):
try:
# Watching Issues Count
watching_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=IssueSubscriber.objects.filter(
workspace__slug=slug, subscriber_id=request.user.id
).values_list("issue_id", flat=True),
).count()
# My Issues Count
my_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=IssueAssignee.objects.filter(
workspace__slug=slug, assignee_id=request.user.id
).values_list("issue_id", flat=True),
).count()
# Created Issues Count
created_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=Issue.objects.filter(
workspace__slug=slug, created_by=request.user
).values_list("pk", flat=True),
).count()
return Response(
{
"watching_issues": watching_issues_count,
"my_issues": my_issues_count,
"created_issues": created_issues_count,
},
status=status.HTTP_200_OK,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+34 -7
View File
@@ -37,7 +37,9 @@ class UserEndpoint(BaseViewSet):
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=request.user.email
).count()
assigned_issues = Issue.issue_objects.filter(assignees__in=[request.user]).count()
assigned_issues = Issue.issue_objects.filter(
assignees__in=[request.user]
).count()
serialized_data = UserSerializer(request.user).data
serialized_data["workspace"] = {
@@ -47,7 +49,9 @@ class UserEndpoint(BaseViewSet):
"fallback_workspace_slug": workspace.slug,
"invites": workspace_invites,
}
serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues
serialized_data.setdefault("issues", {})[
"assigned_issues"
] = assigned_issues
return Response(
serialized_data,
@@ -59,11 +63,15 @@ class UserEndpoint(BaseViewSet):
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=request.user.email
).count()
assigned_issues = Issue.issue_objects.filter(assignees__in=[request.user]).count()
assigned_issues = Issue.issue_objects.filter(
assignees__in=[request.user]
).count()
fallback_workspace = Workspace.objects.filter(
workspace_member__member=request.user
).order_by("created_at").first()
fallback_workspace = (
Workspace.objects.filter(workspace_member__member=request.user)
.order_by("created_at")
.first()
)
serialized_data = UserSerializer(request.user).data
@@ -78,7 +86,9 @@ class UserEndpoint(BaseViewSet):
else None,
"invites": workspace_invites,
}
serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues
serialized_data.setdefault("issues", {})[
"assigned_issues"
] = assigned_issues
return Response(
serialized_data,
@@ -109,6 +119,23 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
)
class UpdateUserTourCompletedEndpoint(BaseAPIView):
def patch(self, request):
try:
user = User.objects.get(pk=request.user.id)
user.is_tour_completed = request.data.get("is_tour_completed", False)
user.save()
return Response(
{"message": "Updated successfully"}, status=status.HTTP_200_OK
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class UserActivityEndpoint(BaseAPIView, BasePaginator):
def get(self, request):
try:
+12 -5
View File
@@ -96,6 +96,7 @@ class ProjectViewSet(BaseViewSet):
def list(self, request, slug):
try:
is_favorite = request.GET.get("is_favorite", "all")
subquery = ProjectFavorite.objects.filter(
user=self.request.user,
project_id=OuterRef("pk"),
@@ -126,6 +127,12 @@ class ProjectViewSet(BaseViewSet):
.values("count")
)
)
if is_favorite == "true":
projects = projects.filter(is_favorite=True)
if is_favorite == "false":
projects = projects.filter(is_favorite=False)
return Response(ProjectDetailSerializer(projects, many=True).data)
except Exception as e:
capture_exception(e)
@@ -153,32 +160,32 @@ class ProjectViewSet(BaseViewSet):
states = [
{
"name": "Backlog",
"color": "#5e6ad2",
"color": "#A3A3A3",
"sequence": 15000,
"group": "backlog",
"default": True,
},
{
"name": "Todo",
"color": "#eb5757",
"color": "#3A3A3A",
"sequence": 25000,
"group": "unstarted",
},
{
"name": "In Progress",
"color": "#26b5ce",
"color": "#F59E0B",
"sequence": 35000,
"group": "started",
},
{
"name": "Done",
"color": "#f2c94c",
"color": "#16A34A",
"sequence": 45000,
"group": "completed",
},
{
"name": "Cancelled",
"color": "#4cb782",
"color": "#EF4444",
"sequence": 55000,
"group": "cancelled",
},
+1
View File
@@ -101,6 +101,7 @@ class WorkSpaceViewSet(BaseViewSet):
.filter(workspace_member__member=self.request.user)
.annotate(total_members=member_count)
.annotate(total_issues=issue_count)
.select_related("owner")
)
def create(self, request):
+148 -67
View File
@@ -558,22 +558,62 @@ def track_estimate_points(
)
def track_archive_in(
def track_archive_at(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} has restored the issue",
verb="updated",
actor=actor,
field="archvied_at",
old_value="archive",
new_value="restore",
if requested_data.get("archived_at") is None:
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} has restored the issue",
verb="updated",
actor=actor,
field="archived_at",
old_value="archive",
new_value="restore",
)
)
else:
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"Plane has archived the issue",
verb="updated",
actor=actor,
field="archived_at",
old_value=None,
new_value="archive",
)
)
def track_closed_to(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
if requested_data.get("closed_to") is not None:
updated_state = State.objects.get(
pk=requested_data.get("closed_to"), project=project
)
issue_activities.append(
IssueActivity(
issue_id=issue_id,
actor=actor,
verb="updated",
old_value=None,
new_value=updated_state.name,
field="state",
project=project,
workspace=project.workspace,
comment=f"Plane updated the state to {updated_state.name}",
old_identifier=None,
new_identifier=updated_state.id,
)
)
)
def update_issue_activity(
@@ -592,7 +632,8 @@ def update_issue_activity(
"blocks_list": track_blocks,
"blockers_list": track_blockings,
"estimate_point": track_estimate_points,
"archived_in": track_archive_in,
"archived_at": track_archive_at,
"closed_to": track_closed_to,
}
requested_data = json.loads(requested_data) if requested_data is not None else None
@@ -970,11 +1011,16 @@ def delete_attachment_activity(
)
# Receive message from room group
@shared_task
def issue_activity(
type, requested_data, current_instance, issue_id, actor_id, project_id
type,
requested_data,
current_instance,
issue_id,
actor_id,
project_id,
subscriber=True,
):
try:
issue_activities = []
@@ -982,16 +1028,26 @@ def issue_activity(
actor = User.objects.get(pk=actor_id)
project = Project.objects.get(pk=project_id)
issue = Issue.objects.filter(pk=issue_id).first()
if issue is not None:
issue.updated_at = timezone.now()
issue.save()
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.create(issue_id=issue_id, subscriber=actor)
except Exception as e:
pass
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
]:
issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first()
if issue is not None:
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
if subscriber:
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.get_or_create(
issue_id=issue_id, subscriber=actor
)
except Exception as e:
pass
ACTIVITY_MAPPER = {
"issue.activity.created": create_issue_activity,
@@ -1043,55 +1099,80 @@ def issue_activity(
except Exception as e:
capture_exception(e)
# Create Notifications
bulk_notifications = []
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
]:
# Create Notifications
bulk_notifications = []
issue_subscribers = list(
IssueSubscriber.objects.filter(project=project, issue_id=issue_id)
.exclude(subscriber_id=actor_id)
.values_list("subscriber", flat=True)
)
issue_subscribers = list(
IssueSubscriber.objects.filter(project=project, issue_id=issue_id)
.exclude(subscriber_id=actor_id)
.values_list("subscriber", flat=True)
)
issue_assignees = list(
IssueAssignee.objects.filter(project=project, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.values_list("assignee", flat=True)
)
issue_assignees = list(
IssueAssignee.objects.filter(project=project, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.values_list("assignee", flat=True)
)
issue_subscribers = issue_subscribers + issue_assignees
issue_subscribers = issue_subscribers + issue_assignees
if issue.created_by_id:
issue_subscribers = issue_subscribers + [issue.created_by_id]
issue = Issue.objects.filter(pk=issue_id, project_id=project_id).first()
issue = Issue.objects.get(project=project, pk=issue_id)
for subscriber in issue_subscribers:
for issue_activity in issue_activities_created:
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
entity_name="issue",
project=project,
title=issue_activity.comment,
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
# Add bot filtering
if (
issue is not None
and issue.created_by_id is not None
and not issue.created_by.is_bot
and str(issue.created_by_id) != str(actor_id)
):
issue_subscribers = issue_subscribers + [issue.created_by_id]
for subscriber in issue_subscribers:
for issue_activity in issue_activities_created:
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
entity_name="issue",
project=project,
title=issue_activity.comment,
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(issue_activity.id),
"verb": str(issue_activity.verb),
"field": str(issue_activity.field),
"actor": str(issue_activity.actor_id),
"new_value": str(issue_activity.new_value),
"old_value": str(issue_activity.old_value),
"issue_comment": str(
issue_activity.issue_comment.comment_stripped
if issue_activity.issue_comment is not None
else ""
),
},
},
"issue_activity": str(issue_activity.id),
},
)
)
)
# Bulk create notifications
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
# Bulk create notifications
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
return
except Exception as e:
@@ -1,4 +1,5 @@
# Python improts
# Python imports
import json
from datetime import timedelta
# Django imports
@@ -11,7 +12,8 @@ from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import Issue, Project, IssueActivity, State
from plane.db.models import Issue, Project, State
from plane.bgtasks.issue_activites_task import issue_activity
@shared_task
@@ -19,6 +21,7 @@ def archive_and_close_old_issues():
archive_old_issues()
close_old_issues()
def archive_old_issues():
try:
# Get all the projects whose archive_in is greater than 0
@@ -46,6 +49,11 @@ def archive_old_issues():
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
).filter(
Q(issue_inbox__status=1)
| Q(issue_inbox__status=-1)
| Q(issue_inbox__status=2)
| Q(issue_inbox__isnull=True)
)
# Check if Issues
@@ -56,24 +64,21 @@ def archive_old_issues():
issues_to_update.append(issue)
# Bulk Update the issues and log the activity
Issue.objects.bulk_update(issues_to_update, ["archived_at"], batch_size=100)
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue_id=issue.id,
actor=project.created_by,
verb="updated",
field="archived_at",
project=project,
workspace=project.workspace,
comment="Plane archived the issue",
new_value="archive",
old_value=""
)
for issue in issues_to_update
],
batch_size=100,
Issue.objects.bulk_update(
issues_to_update, ["archived_at"], batch_size=100
)
[
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(issue.archived_at)}),
actor_id=str(project.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
subscriber=False,
)
for issue in issues_to_update
]
return
except Exception as e:
if settings.DEBUG:
@@ -81,10 +86,13 @@ def archive_old_issues():
capture_exception(e)
return
def close_old_issues():
try:
# Get all the projects whose close_in is greater than 0
projects = Project.objects.filter(close_in__gt=0).select_related("default_state")
projects = Project.objects.filter(close_in__gt=0).select_related(
"default_state"
)
for project in projects:
project_id = project.id
@@ -108,15 +116,19 @@ def close_old_issues():
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
).filter(
Q(issue_inbox__status=1)
| Q(issue_inbox__status=-1)
| Q(issue_inbox__status=2)
| Q(issue_inbox__isnull=True)
)
# Check if Issues
if issues:
if project.default_state is None:
close_state = project.default_state
else:
close_state = State.objects.filter(group="cancelled").first()
else:
close_state = project.default_state
issues_to_update = []
for issue in issues:
@@ -125,24 +137,21 @@ def close_old_issues():
# Bulk Update the issues and log the activity
Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100)
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue_id=issue.id,
actor=project.created_by,
verb="updated",
field="state",
project=project,
workspace=project.workspace,
comment="Plane cancelled the issue",
)
for issue in issues_to_update
],
batch_size=100,
)
[
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"closed_to": str(issue.state_id)}),
actor_id=str(project.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
subscriber=False,
)
for issue in issues_to_update
]
return
except Exception as e:
if settings.DEBUG:
print(e)
capture_exception(e)
return
return
@@ -0,0 +1,266 @@
# Generated by Django 4.2.3 on 2023-07-19 06:52
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import plane.db.models.user
import uuid
def onboarding_default_steps(apps, schema_editor):
default_onboarding_schema = {
"workspace_join": True,
"profile_complete": True,
"workspace_create": True,
"workspace_invite": True,
}
Model = apps.get_model("db", "User")
updated_user = []
for obj in Model.objects.filter(is_onboarded=True):
obj.onboarding_step = default_onboarding_schema
obj.is_tour_completed = True
updated_user.append(obj)
Model.objects.bulk_update(updated_user, ["onboarding_step", "is_tour_completed"], batch_size=100)
class Migration(migrations.Migration):
dependencies = [
("db", "0036_alter_workspace_organization_size"),
]
operations = [
migrations.AddField(
model_name="issue",
name="archived_at",
field=models.DateField(null=True),
),
migrations.AddField(
model_name="project",
name="archive_in",
field=models.IntegerField(
default=0,
validators=[
django.core.validators.MinValueValidator(0),
django.core.validators.MaxValueValidator(12),
],
),
),
migrations.AddField(
model_name="project",
name="close_in",
field=models.IntegerField(
default=0,
validators=[
django.core.validators.MinValueValidator(0),
django.core.validators.MaxValueValidator(12),
],
),
),
migrations.AddField(
model_name="project",
name="default_state",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="default_state",
to="db.state",
),
),
migrations.AddField(
model_name="user",
name="is_tour_completed",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user",
name="onboarding_step",
field=models.JSONField(default=plane.db.models.user.get_default_onboarding),
),
migrations.RunPython(onboarding_default_steps),
migrations.CreateModel(
name="Notification",
fields=[
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Created At"),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("data", models.JSONField(null=True)),
("entity_identifier", models.UUIDField(null=True)),
("entity_name", models.CharField(max_length=255)),
("title", models.TextField()),
("message", models.JSONField(null=True)),
("message_html", models.TextField(blank=True, default="<p></p>")),
("message_stripped", models.TextField(blank=True, null=True)),
("sender", models.CharField(max_length=255)),
("read_at", models.DateTimeField(null=True)),
("snoozed_till", models.DateTimeField(null=True)),
("archived_at", models.DateTimeField(null=True)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"project",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications",
to="db.project",
),
),
(
"receiver",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="received_notifications",
to=settings.AUTH_USER_MODEL,
),
),
(
"triggered_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="triggered_notifications",
to=settings.AUTH_USER_MODEL,
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications",
to="db.workspace",
),
),
],
options={
"verbose_name": "Notification",
"verbose_name_plural": "Notifications",
"db_table": "notifications",
"ordering": ("-created_at",),
},
),
migrations.CreateModel(
name="IssueSubscriber",
fields=[
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Created At"),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="issue_subscribers",
to="db.issue",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"subscriber",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="issue_subscribers",
to=settings.AUTH_USER_MODEL,
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"verbose_name": "Issue Subscriber",
"verbose_name_plural": "Issue Subscribers",
"db_table": "issue_subscribers",
"ordering": ("-created_at",),
"unique_together": {("issue", "subscriber")},
},
),
]
@@ -0,0 +1,35 @@
# Generated by Django 4.2.3 on 2023-07-20 09:35
from django.db import migrations, models
def restructure_theming(apps, schema_editor):
Model = apps.get_model("db", "User")
updated_user = []
for obj in Model.objects.exclude(theme={}).all():
current_theme = obj.theme
updated_theme = {
"primary": current_theme.get("accent", ""),
"background": current_theme.get("bgBase", ""),
"sidebarBackground": current_theme.get("sidebar", ""),
"text": current_theme.get("textBase", ""),
"sidebarText": current_theme.get("textBase", ""),
"palette": f"""{current_theme.get("bgBase","")},{current_theme.get("textBase", "")},{current_theme.get("accent", "")},{current_theme.get("sidebar","")},{current_theme.get("textBase", "")}""",
"darkPalette": current_theme.get("darkPalette", "")
}
obj.theme = updated_theme
updated_user.append(obj)
Model.objects.bulk_update(
updated_user, ["theme"], batch_size=100
)
class Migration(migrations.Migration):
dependencies = [
("db", "0037_issue_archived_at_project_archive_in_and_more"),
]
operations = [
migrations.RunPython(restructure_theming)
]
-1
View File
@@ -28,7 +28,6 @@ class IssueManager(models.Manager):
| models.Q(issue_inbox__status=2)
| models.Q(issue_inbox__isnull=True)
)
.filter(archived_at__isnull=True)
.exclude(archived_at__isnull=False)
)
+9
View File
@@ -18,6 +18,13 @@ from sentry_sdk import capture_exception
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
def get_default_onboarding():
return {
"profile_complete": False,
"workspace_create": False,
"workspace_invite": False,
"workspace_join": False,
}
class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(
@@ -73,6 +80,8 @@ class User(AbstractBaseUser, PermissionsMixin):
role = models.CharField(max_length=300, null=True, blank=True)
is_bot = models.BooleanField(default=False)
theme = models.JSONField(default=dict)
is_tour_completed = models.BooleanField(default=False)
onboarding_step = models.JSONField(default=get_default_onboarding)
USERNAME_FIELD = "email"
+1 -1
View File
@@ -17,7 +17,7 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("PGUSER", "plane"),
"USER": "",
"PASSWORD": "",
+1 -1
View File
@@ -17,7 +17,7 @@ DEBUG = int(os.environ.get("DEBUG", 0)) == 1
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"ENGINE": "django.db.backends.postgresql",
"NAME": "plane",
"USER": os.environ.get("PGUSER", ""),
"PASSWORD": os.environ.get("PGPASSWORD", ""),
+1 -1
View File
@@ -16,7 +16,7 @@ from .common import * # noqa
DEBUG = int(os.environ.get("DEBUG", 1)) == 1
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("PGUSER", "plane"),
"USER": "",
"PASSWORD": "",
+3 -1
View File
@@ -3,7 +3,7 @@
Django==4.2.3
django-braces==1.15.0
django-taggit==4.0.0
psycopg2==2.9.6
psycopg==3.1.9
django-oauth-toolkit==2.3.0
mistune==3.0.1
djangorestframework==3.14.0
@@ -30,3 +30,5 @@ openai==0.27.8
slack-sdk==3.21.3
celery==5.3.1
django_celery_beat==2.5.0
psycopg-binary==3.1.9
psycopg-c==3.1.9
+1 -1
View File
@@ -7,5 +7,5 @@ django-storages==1.13.2
boto3==1.27.0
django-anymail==10.0
django-debug-toolbar==4.1.0
gevent==22.10.2
gevent==23.7.0
psycogreen==1.0.2
+49 -58
View File
@@ -32,6 +32,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
setError,
setValue,
getValues,
watch,
formState: { errors, isSubmitting, isValid, isDirty },
} = useForm<EmailCodeFormValues>({
defaultValues: {
@@ -112,43 +113,35 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
return (
<>
<form className="space-y-5 py-5 px-5">
{(codeSent || codeResent) && (
<div className="rounded-md bg-green-500/20 p-4">
<div className="flex">
<div className="flex-shrink-0">
<CheckCircleIcon className="h-5 w-5 text-green-500" aria-hidden="true" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-green-500">
{codeResent
? "Please check your mail for new code."
: "Please check your mail for code."}
</p>
</div>
</div>
</div>
)}
<div>
{(codeSent || codeResent) && (
<p className="text-center mt-4">
We have sent the sign in code.
<br />
Please check your inbox at <span className="font-medium">{watch("email")}</span>
</p>
)}
<form className="space-y-4 mt-10 sm:w-[360px] mx-auto">
<div className="space-y-1">
<Input
id="email"
type="email"
name="email"
register={register}
validations={{
required: "Email ID is required",
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || "Email ID is not valid",
) || "Email address is not valid",
}}
error={errors.email}
placeholder="Enter your Email ID"
placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]"
/>
</div>
{codeSent && (
<div>
<>
<Input
id="token"
type="token"
@@ -158,14 +151,15 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
required: "Code is required",
}}
error={errors.token}
placeholder="Enter code"
placeholder="Enter code..."
className="border-custom-border-300 h-[46px]"
/>
<button
type="button"
className={`mt-5 flex w-full justify-end text-xs outline-none ${
className={`flex w-full justify-end text-xs outline-none ${
isResendDisabled
? "cursor-default text-custom-text-200"
: "cursor-pointer text-custom-primary"
: "cursor-pointer text-custom-primary-100"
} `}
onClick={() => {
setIsCodeResending(true);
@@ -178,46 +172,43 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
disabled={isResendDisabled}
>
{resendCodeTimer > 0 ? (
<p className="text-right">
Didn{"'"}t receive code? Get new code in {resendCodeTimer} seconds.
</p>
<span className="text-right">Request new code in {resendCodeTimer} seconds</span>
) : isCodeResending ? (
"Sending code..."
"Sending new code..."
) : errorResendingCode ? (
"Please try again later"
) : (
"Resend code"
<span className="font-medium">Resend code</span>
)}
</button>
</div>
</>
)}
{codeSent ? (
<PrimaryButton
type="submit"
className="w-full text-center h-[46px]"
size="md"
onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty}
loading={isLoading}
>
{isLoading ? "Signing in..." : "Sign in"}
</PrimaryButton>
) : (
<PrimaryButton
className="w-full text-center h-[46px]"
size="md"
onClick={() => {
handleSubmit(onSubmit)().then(() => {
setResendCodeTimer(30);
});
}}
disabled={!isValid && isDirty}
loading={isSubmitting}
>
{isSubmitting ? "Sending code..." : "Send sign in code"}
</PrimaryButton>
)}
<div>
{codeSent ? (
<PrimaryButton
type="submit"
className="w-full text-center"
size="md"
onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty}
loading={isLoading}
>
{isLoading ? "Signing in..." : "Sign in"}
</PrimaryButton>
) : (
<PrimaryButton
className="w-full text-center"
size="md"
onClick={() => {
handleSubmit(onSubmit)().then(() => {
setResendCodeTimer(30);
});
}}
loading={isSubmitting || (!isValid && isDirty)}
>
{isSubmitting ? "Sending code..." : "Send code"}
</PrimaryButton>
)}
</div>
</form>
</>
);
@@ -8,7 +8,7 @@ import { useForm } from "react-hook-form";
// components
import { EmailResetPasswordForm } from "components/account";
// ui
import { Input, SecondaryButton } from "components/ui";
import { Input, PrimaryButton } from "components/ui";
// types
type EmailPasswordFormValues = {
email: string;
@@ -42,28 +42,39 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
return (
<>
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
{isResettingPassword
? "Reset your password"
: isSignUpPage
? "Sign up on Plane"
: "Sign in to Plane"}
</h1>
{isResettingPassword ? (
<EmailResetPasswordForm setIsResettingPassword={setIsResettingPassword} />
) : (
<form className="mt-5 py-5 px-5" onSubmit={handleSubmit(onSubmit)}>
<div>
<form
className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto"
onSubmit={handleSubmit(onSubmit)}
>
<div className="space-y-1">
<Input
id="email"
type="email"
name="email"
register={register}
validations={{
required: "Email ID is required",
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || "Email ID is not valid",
) || "Email address is not valid",
}}
error={errors.email}
placeholder="Enter your email ID"
placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]"
/>
</div>
<div className="mt-5">
<div className="space-y-1">
<Input
id="password"
type="password"
@@ -73,46 +84,45 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
required: "Password is required",
}}
error={errors.password}
placeholder="Enter your password"
placeholder="Enter your password..."
className="border-custom-border-300 h-[46px]"
/>
</div>
<div className="mt-2 flex items-center justify-between">
<div className="ml-auto text-sm">
{isSignUpPage ? (
<Link href="/">
<a className="font-medium text-custom-primary hover:text-custom-primary">
Already have an account? Sign in.
</a>
</Link>
) : (
<button
type="button"
onClick={() => setIsResettingPassword(true)}
className="font-medium text-custom-primary hover:text-custom-primary"
>
Forgot your password?
</button>
)}
</div>
<div className="text-right text-xs">
{isSignUpPage ? (
<Link href="/">
<a className="text-custom-text-200 hover:text-custom-primary-100">
Already have an account? Sign in.
</a>
</Link>
) : (
<button
type="button"
onClick={() => setIsResettingPassword(true)}
className="text-custom-text-200 hover:text-custom-primary-100"
>
Forgot your password?
</button>
)}
</div>
<div className="mt-5">
<SecondaryButton
<div>
<PrimaryButton
type="submit"
className="w-full text-center"
className="w-full text-center h-[46px]"
disabled={!isValid && isDirty}
loading={isSubmitting}
>
{isSignUpPage
? isSubmitting
? "Signing up..."
: "Sign Up"
: "Sign up"
: isSubmitting
? "Signing in..."
: "Sign In"}
</SecondaryButton>
: "Sign in"}
</PrimaryButton>
{!isSignUpPage && (
<Link href="/sign-up">
<a className="block font-medium text-custom-primary hover:text-custom-primary text-sm mt-1">
<a className="block text-custom-text-200 hover:text-custom-primary-100 text-xs mt-4">
Don{"'"}t have an account? Sign up.
</a>
</Link>
@@ -59,32 +59,36 @@ export const EmailResetPasswordForm: React.FC<Props> = ({ setIsResettingPassword
};
return (
<form className="mt-5 py-5 px-5" onSubmit={handleSubmit(forgotPassword)}>
<div>
<form
className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto"
onSubmit={handleSubmit(forgotPassword)}
>
<div className="space-y-1">
<Input
id="email"
type="email"
name="email"
register={register}
validations={{
required: "Email ID is required",
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || "Email ID is not valid",
) || "Email address is not valid",
}}
error={errors.email}
placeholder="Enter registered Email ID"
placeholder="Enter registered email address.."
className="border-custom-border-300 h-[46px]"
/>
</div>
<div className="mt-5 flex items-center gap-2">
<div className="mt-5 flex flex-col-reverse sm:flex-row items-center gap-2">
<SecondaryButton
className="w-full text-center"
className="w-full text-center h-[46px]"
onClick={() => setIsResettingPassword(false)}
>
Go Back
</SecondaryButton>
<PrimaryButton type="submit" className="w-full text-center" loading={isSubmitting}>
<PrimaryButton type="submit" className="w-full text-center h-[46px]" loading={isSubmitting}>
{isSubmitting ? "Sending link..." : "Send reset link"}
</PrimaryButton>
</div>
@@ -1,9 +1,14 @@
import { useEffect, useState, FC } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
// next-themes
import { useTheme } from "next-themes";
// images
import githubImage from "/public/logos/github-black.png";
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
const { NEXT_PUBLIC_GITHUB_ID } = process.env;
@@ -11,15 +16,15 @@ export interface GithubLoginButtonProps {
handleSignIn: React.Dispatch<string>;
}
export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
const { handleSignIn } = props;
// router
export const GithubLoginButton: FC<GithubLoginButtonProps> = ({ handleSignIn }) => {
const [loginCallBackURL, setLoginCallBackURL] = useState(undefined);
const [gitCode, setGitCode] = useState<null | string>(null);
const {
query: { code },
} = useRouter();
// states
const [loginCallBackURL, setLoginCallBackURL] = useState(undefined);
const [gitCode, setGitCode] = useState<null | string>(null);
const { theme } = useTheme();
useEffect(() => {
if (code && !gitCode) {
@@ -35,13 +40,18 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
}, []);
return (
<div className="w-full flex justify-center items-center px-[3px]">
<div className="w-full flex justify-center items-center">
<Link
href={`https://github.com/login/oauth/authorize?client_id=${NEXT_PUBLIC_GITHUB_ID}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
>
<button className="flex w-full items-center justify-center gap-3 rounded border border-custom-border-100 p-2 text-sm font-medium text-custom-text-200 duration-300 hover:bg-custom-background-80">
<Image src={githubImage} height={20} width={20} color="#000" alt="GitHub Logo" />
<span>Sign In with Github</span>
<button className="flex w-full items-center justify-center gap-2 rounded border border-custom-border-300 p-2 text-sm font-medium text-custom-text-100 duration-300 hover:bg-custom-background-80 h-[46px]">
<Image
src={theme === "dark" ? githubWhiteImage : githubBlackImage}
height={20}
width={20}
alt="GitHub Logo"
/>
<span>Sign in with GitHub</span>
</button>
</Link>
</div>
+9 -7
View File
@@ -1,5 +1,5 @@
import { FC, CSSProperties, useEffect, useRef, useCallback, useState } from "react";
// next
import Script from "next/script";
export interface IGoogleLoginButton {
@@ -8,18 +8,18 @@ export interface IGoogleLoginButton {
styles?: CSSProperties;
}
export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
const { handleSignIn } = props;
export const GoogleLoginButton: FC<IGoogleLoginButton> = ({ handleSignIn }) => {
const googleSignInButton = useRef<HTMLDivElement>(null);
const [gsiScriptLoaded, setGsiScriptLoaded] = useState(false);
const loadScript = useCallback(() => {
if (!googleSignInButton.current || gsiScriptLoaded) return;
window?.google?.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENTID || "",
callback: handleSignIn,
});
window?.google?.accounts.id.renderButton(
googleSignInButton.current,
{
@@ -27,11 +27,13 @@ export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
theme: "outline",
size: "large",
logo_alignment: "center",
width: "410",
text: "continue_with",
width: "360",
text: "signin_with",
} as GsiButtonConfiguration // customization attributes
);
window?.google?.accounts.id.prompt(); // also display the One Tap dialog
setGsiScriptLoaded(true);
}, [handleSignIn, gsiScriptLoaded]);
@@ -48,7 +50,7 @@ export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
<>
<Script src="https://accounts.google.com/gsi/client" async defer onLoad={loadScript} />
<div
className="overflow-hidden rounded w-full flex justify-center items-center"
className="overflow-hidden rounded w-full flex justify-center items-center !text-sm !font-medium !text-custom-text-100"
id="googleSignInButton"
ref={googleSignInButton}
/>
@@ -111,7 +111,7 @@ export const CreateUpdateAnalyticsModal: React.FC<Props> = ({ isOpen, handleClos
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title
@@ -31,7 +31,7 @@ export const CustomTooltip: React.FC<Props> = ({ datum, analytics, params }) =>
}
return (
<div className="flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="flex items-center gap-2 rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
<span
className="h-3 w-3 rounded"
style={{
@@ -185,7 +185,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
<div
className={`px-5 py-2.5 flex items-center justify-between space-y-2 ${
fullScreen
? "border-l border-custom-border-100 md:h-full md:border-l md:border-custom-border-100 md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
? "border-l border-custom-border-200 md:h-full md:border-l md:border-custom-border-200 md:space-y-4 overflow-hidden md:flex-col md:items-start md:py-5"
: ""
}`}
>
@@ -37,9 +37,9 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
<div className="flow-root">
<div className="overflow-x-auto">
<div className="inline-block min-w-full align-middle">
<table className="min-w-full divide-y divide-custom-border-100 whitespace-nowrap border-y border-custom-border-100">
<table className="min-w-full divide-y divide-custom-border-200 whitespace-nowrap border-y border-custom-border-200">
<thead className="bg-custom-background-80">
<tr className="divide-x divide-custom-border-100 text-sm text-custom-text-100">
<tr className="divide-x divide-custom-border-200 text-sm text-custom-text-100">
<th scope="col" className="py-3 px-2.5 text-left font-medium">
{ANALYTICS_X_AXIS_VALUES.find((v) => v.value === params.x_axis)?.label}
</th>
@@ -80,11 +80,11 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
)}
</tr>
</thead>
<tbody className="divide-y divide-custom-border-100">
<tbody className="divide-y divide-custom-border-200">
{barGraphData.data.map((item, index) => (
<tr
key={`table-row-${index}`}
className="divide-x divide-custom-border-100 text-xs text-custom-text-200"
className="divide-x divide-custom-border-200 text-xs text-custom-text-200"
>
<td
className={`flex items-center gap-2 whitespace-nowrap py-2 px-2.5 font-medium ${
@@ -155,7 +155,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
} ${isOpen ? "right-0" : "-right-full"} duration-300 transition-all`}
>
<div
className={`flex h-full flex-col overflow-hidden border-custom-border-100 bg-custom-background-100 text-left ${
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
fullScreen ? "rounded-lg border" : "border-l"
}`}
>
@@ -186,12 +186,12 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
</div>
</div>
<Tab.Group as={Fragment}>
<Tab.List as="div" className="space-x-2 border-b border-custom-border-100 p-5 pt-0">
<Tab.List as="div" className="space-x-2 border-b border-custom-border-200 p-5 pt-0">
{tabsList.map((tab) => (
<Tab
key={tab}
className={({ selected }) =>
`rounded-3xl border border-custom-border-100 px-4 py-2 text-xs hover:bg-custom-background-80 ${
`rounded-3xl border border-custom-border-200 px-4 py-2 text-xs hover:bg-custom-background-80 ${
selected ? "bg-custom-background-80" : ""
}`
}
@@ -10,7 +10,7 @@ type Props = {
};
export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<div className="space-y-3 rounded-[10px] border border-custom-border-100 p-3">
<div className="space-y-3 rounded-[10px] border border-custom-border-200 p-3">
<h5 className="text-xs text-red-500">DEMAND</h5>
<div>
<h4 className="text-custom-text-100 text-base font-medium">Total open tasks</h4>
@@ -50,7 +50,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
);
})}
</div>
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="!mt-6 flex w-min items-center gap-2 whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
<p className="flex items-center gap-1 text-custom-text-200">
<PlayIcon className="h-4 w-4 -rotate-90" aria-hidden="true" />
<span>Estimate Demand:</span>
@@ -10,7 +10,7 @@ type Props = {
};
export const AnalyticsLeaderboard: React.FC<Props> = ({ users, title }) => (
<div className="p-3 border border-custom-border-100 rounded-[10px]">
<div className="p-3 border border-custom-border-200 rounded-[10px]">
<h6 className="text-base font-medium">{title}</h6>
{users.length > 0 ? (
<div className="mt-3 space-y-3">
@@ -8,9 +8,9 @@ type Props = {
};
export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
<div className="rounded-[10px] border border-custom-border-100">
<div className="rounded-[10px] border border-custom-border-200">
<h5 className="p-3 text-xs text-green-500">SCOPE</h5>
<div className="divide-y divide-custom-border-100">
<div className="divide-y divide-custom-border-200">
<div>
<h6 className="px-3 text-base font-medium">Pending issues</h6>
{defaultAnalytics.pending_issue_user.length > 0 ? (
@@ -27,7 +27,7 @@ export const AnalyticsScope: React.FC<Props> = ({ defaultAnalytics }) => (
);
return (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
<span className="font-medium text-custom-text-200">
{assignee
? assignee.assignees__first_name + " " + assignee.assignees__last_name
@@ -9,54 +9,46 @@ type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
};
export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) => {
const currentMonth = new Date().getMonth();
const startMonth = Math.floor(currentMonth / 3) * 3 + 1;
const quarterMonthsList = [startMonth, startMonth + 1, startMonth + 2];
return (
<div className="py-3 border border-custom-border-100 rounded-[10px]">
<h1 className="px-3 text-base font-medium">Issues closed in a year</h1>
{defaultAnalytics.issue_completed_month_wise.length > 0 ? (
<LineGraph
data={[
{
id: "issues_closed",
color: "rgb(var(--color-primary-100))",
data: MONTHS_LIST.map((month) => ({
x: month.label.substring(0, 3),
y:
defaultAnalytics.issue_completed_month_wise.find(
(data) => data.month === month.value
)?.count || 0,
})),
},
]}
customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => {
if (quarterMonthsList.includes(data.month)) return data.count;
return 0;
})}
height="300px"
colors={(datum) => datum.color}
curve="monotoneX"
margin={{ top: 20 }}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
{datum.slice.points[0].data.yFormatted}
<span className="text-custom-text-200"> issues closed in </span>
{datum.slice.points[0].data.xFormatted}
</div>
)}
theme={{
background: "rgb(var(--color-background-100))",
}}
enableArea
/>
) : (
<div className="text-custom-text-200 text-center text-sm py-8">No matching data found.</div>
)}
</div>
);
};
export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) => (
<div className="py-3 border border-custom-border-200 rounded-[10px]">
<h1 className="px-3 text-base font-medium">Issues closed in a year</h1>
{defaultAnalytics.issue_completed_month_wise.length > 0 ? (
<LineGraph
data={[
{
id: "issues_closed",
color: "rgb(var(--color-primary-100))",
data: MONTHS_LIST.map((month) => ({
x: month.label.substring(0, 3),
y:
defaultAnalytics.issue_completed_month_wise.find(
(data) => data.month === month.value
)?.count || 0,
})),
},
]}
customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map(
(data) => data.count
)}
height="300px"
colors={(datum) => datum.color}
curve="monotoneX"
margin={{ top: 20 }}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
{datum.slice.points[0].data.yFormatted}
<span className="text-custom-text-200"> issues closed in </span>
{datum.slice.points[0].data.xFormatted}
</div>
)}
theme={{
background: "rgb(var(--color-background-100))",
}}
enableArea
/>
) : (
<div className="text-custom-text-200 text-center text-sm py-8">No matching data found.</div>
)}
</div>
);
@@ -22,7 +22,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
return (
<DefaultLayout>
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-90 text-center">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-100 text-center">
<div className="h-44 w-72">
<Image
src={type === "project" ? ProjectNotAuthorizedImg : WorkspaceNotAuthorizedImg}
@@ -41,7 +41,7 @@ export const JoinProject: React.FC = () => {
};
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-90 text-center">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 bg-custom-background-100 text-center">
<div className="h-44 w-72">
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
</div>
@@ -0,0 +1,90 @@
import React, { useState } from "react";
// component
import { CustomSelect, ToggleSwitch } from "components/ui";
import { SelectMonthModal } from "components/automation";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
// constants
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
// types
import { IProject } from "types";
type Props = {
projectDetails: IProject | undefined;
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleChange }) => {
const [monthModal, setmonthModal] = useState(false);
const initialValues: Partial<IProject> = { archive_in: 1 };
return (
<>
<SelectMonthModal
type="auto-archive"
initialValues={initialValues}
isOpen={monthModal}
handleClose={() => setmonthModal(false)}
handleChange={handleChange}
/>
<div className="flex flex-col gap-7 px-6 py-5 rounded-[10px] border border-custom-border-100 bg-custom-background-90">
<div className="flex items-center justify-between gap-x-8 gap-y-2">
<div className="flex flex-col gap-2.5">
<h4 className="text-lg font-semibold">Auto-archive closed issues</h4>
<p className="text-sm text-custom-text-200">
Plane will automatically archive issues that have been completed or cancelled for the
configured time period.
</p>
</div>
<ToggleSwitch
value={projectDetails?.archive_in !== 0}
onChange={() =>
projectDetails?.archive_in === 0
? handleChange({ archive_in: 1 })
: handleChange({ archive_in: 0 })
}
size="sm"
/>
</div>
{projectDetails?.archive_in !== 0 && (
<div className="flex items-center justify-between gap-2 w-full">
<div className="w-1/2 text-base font-medium">
Auto-archive issues that are closed for
</div>
<div className="w-1/2">
<CustomSelect
value={projectDetails?.archive_in}
label={`${projectDetails?.archive_in} ${
projectDetails?.archive_in === 1 ? "Month" : "Months"
}`}
onChange={(val: number) => {
handleChange({ archive_in: val });
}}
input
verticalPosition="top"
width="w-full"
>
<>
{PROJECT_AUTOMATION_MONTHS.map((month) => (
<CustomSelect.Option key={month.label} value={month.value}>
{month.label}
</CustomSelect.Option>
))}
<button
type="button"
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => setmonthModal(true)}
>
Customize Time Range
</button>
</>
</CustomSelect>
</div>
</div>
)}
</div>
</>
);
};
@@ -0,0 +1,177 @@
import React, { useState } from "react";
import useSWR from "swr";
import { useRouter } from "next/router";
// component
import { CustomSearchSelect, CustomSelect, ToggleSwitch } from "components/ui";
import { SelectMonthModal } from "components/automation";
// icons
import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
// services
import stateService from "services/state.service";
// constants
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
import { STATES_LIST } from "constants/fetch-keys";
// types
import { IProject } from "types";
// helper
import { getStatesList } from "helpers/state.helper";
type Props = {
projectDetails: IProject | undefined;
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleChange }) => {
const [monthModal, setmonthModal] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { data: stateGroups } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
workspaceSlug && projectId
? () => stateService.getStates(workspaceSlug as string, projectId as string)
: null
);
const states = getStatesList(stateGroups ?? {});
const options = states
?.filter((state) => state.group === "cancelled")
.map((state) => ({
value: state.id,
query: state.name,
content: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)}
{state.name}
</div>
),
}));
const multipleOptions = options.length > 1;
const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null;
const selectedOption = states?.find(
(s) => s.id === projectDetails?.default_state ?? defaultState
);
const currentDefaultState = states.find((s) => s.id === defaultState);
const initialValues: Partial<IProject> = {
close_in: 1,
default_state: defaultState,
};
return (
<>
<SelectMonthModal
type="auto-close"
initialValues={initialValues}
isOpen={monthModal}
handleClose={() => setmonthModal(false)}
handleChange={handleChange}
/>
<div className="flex flex-col gap-7 px-6 py-5 rounded-[10px] border border-custom-border-100 bg-custom-background-90">
<div className="flex items-center justify-between gap-x-8 gap-y-2 ">
<div className="flex flex-col gap-2.5">
<h4 className="text-lg font-semibold">Auto-close inactive issues</h4>
<p className="text-sm text-custom-text-200">
Plane will automatically close the issues that have not been updated for the
configured time period.
</p>
</div>
<ToggleSwitch
value={projectDetails?.close_in !== 0}
onChange={() =>
projectDetails?.close_in === 0
? handleChange({ close_in: 1, default_state: defaultState })
: handleChange({ close_in: 0, default_state: null })
}
size="sm"
/>
</div>
{projectDetails?.close_in !== 0 && (
<div className="flex flex-col gap-4 w-full">
<div className="flex items-center justify-between gap-2 w-full">
<div className="w-1/2 text-base font-medium">
Auto-close issues that are inactive for
</div>
<div className="w-1/2">
<CustomSelect
value={projectDetails?.close_in}
label={`${projectDetails?.close_in} ${
projectDetails?.close_in === 1 ? "Month" : "Months"
}`}
onChange={(val: number) => {
handleChange({ close_in: val });
}}
input
width="w-full"
>
<>
{PROJECT_AUTOMATION_MONTHS.map((month) => (
<CustomSelect.Option key={month.label} value={month.value}>
{month.label}
</CustomSelect.Option>
))}
<button
type="button"
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => setmonthModal(true)}
>
Customize Time Range
</button>
</>
</CustomSelect>
</div>
</div>
<div className="flex items-center justify-between gap-2 w-full">
<div className="w-1/2 text-base font-medium">Auto-close Status</div>
<div className="w-1/2 ">
<CustomSearchSelect
value={
projectDetails?.default_state ? projectDetails?.default_state : defaultState
}
label={
<div className="flex items-center gap-2">
{selectedOption ? (
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)
) : currentDefaultState ? (
getStateGroupIcon(
currentDefaultState.group,
"16",
"16",
currentDefaultState.color
)
) : (
<Squares2X2Icon className="h-3.5 w-3.5 text-custom-text-200" />
)}
{selectedOption?.name
? selectedOption.name
: currentDefaultState?.name ?? (
<span className="text-custom-text-200">State</span>
)}
</div>
}
onChange={(val: string) => {
handleChange({ default_state: val });
}}
options={options}
disabled={!multipleOptions}
width="w-full"
input
/>
</div>
</div>
</div>
)}
</div>
</>
);
};
+3
View File
@@ -0,0 +1,3 @@
export * from "./auto-close-automation";
export * from "./auto-archive-automation";
export * from "./select-month-modal";
@@ -0,0 +1,147 @@
import React from "react";
import { useRouter } from "next/router";
// react-hook-form
import { useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// types
import type { IProject } from "types";
// types
type Props = {
isOpen: boolean;
type: "auto-close" | "auto-archive";
initialValues: Partial<IProject>;
handleClose: () => void;
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const SelectMonthModal: React.FC<Props> = ({
type,
initialValues,
isOpen,
handleClose,
handleChange,
}) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
reset,
} = useForm<IProject>({
defaultValues: initialValues,
});
const onClose = () => {
handleClose();
reset(initialValues);
};
const onSubmit = (formData: Partial<IProject>) => {
if (!workspaceSlug && !projectId) return;
handleChange(formData);
onClose();
};
const inputSection = (name: string) => (
<div className="relative flex flex-col gap-1 justify-center w-full">
<Input
type="number"
id={name}
name={name}
placeholder="Enter Months"
autoComplete="off"
register={register}
width="full"
validations={{
required: "Select a month between 1 and 12.",
min: 1,
max: 12,
}}
className="border-custom-border-200"
/>
<span className="absolute text-sm text-custom-text-200 top-2.5 right-8">Months</span>
</div>
);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-30" onClose={onClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
Customize Time Range
</Dialog.Title>
<div className="mt-8 flex items-center gap-2">
<div className="flex w-full flex-col gap-1 justify-center">
{type === "auto-close" ? (
<>
{inputSection("close_in")}
{errors.close_in && (
<span className="text-sm px-1 text-red-500">
Select a month between 1 and 12.
</span>
)}
</>
) : (
<>
{inputSection("archive_in")}
{errors.archive_in && (
<span className="text-sm px-1 text-red-500">
Select a month between 1 and 12.
</span>
)}
</>
)}
</div>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</PrimaryButton>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
+2 -2
View File
@@ -17,7 +17,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
<div className="flex items-center">
<button
type="button"
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-custom-sidebar-border-100 text-center text-sm hover:bg-custom-sidebar-background-90"
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-custom-sidebar-border-200 text-center text-sm hover:bg-custom-sidebar-background-90"
onClick={() => router.back()}
>
<Icon
@@ -41,7 +41,7 @@ const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ title, link, icon }) =>
<>
{link ? (
<Link href={link}>
<a className="border-r-2 border-custom-sidebar-border-100 px-3 text-sm">
<a className="border-r-2 border-custom-sidebar-border-200 px-3 text-sm">
<p className={`${icon ? "flex items-center gap-2" : ""}`}>
{icon ?? null}
{title}
@@ -380,7 +380,6 @@ export const CommandPalette: React.FC = () => {
user={user}
/>
)}
<CreateUpdateIssueModal
isOpen={isIssueModalOpen}
handleClose={() => setIsIssueModalOpen(false)}
@@ -421,7 +420,7 @@ export const CommandPalette: React.FC = () => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-custom-border-100 divide-opacity-10 rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-custom-border-200 divide-opacity-10 rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<Command
filter={(value, search) => {
if (value.toLowerCase().includes(search.toLowerCase())) return 1;
@@ -456,7 +455,7 @@ export const CommandPalette: React.FC = () => {
aria-hidden="true"
/>
<Command.Input
className="w-full border-0 border-b border-custom-border-100 bg-transparent p-4 pl-11 text-custom-text-100 outline-none focus:ring-0 sm:text-sm"
className="w-full border-0 border-b border-custom-border-200 bg-transparent p-4 pl-11 text-custom-text-100 outline-none focus:ring-0 sm:text-sm"
placeholder={placeholder}
value={searchTerm}
onValueChange={(e) => {
@@ -104,7 +104,7 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
</span>
</Dialog.Title>
<div>
<div className="flex w-full items-center justify-start gap-1 rounded border-[0.6px] border-custom-border-100 bg-custom-background-90 px-3 py-2">
<div className="flex w-full items-center justify-start gap-1 rounded border-[0.6px] border-custom-border-200 bg-custom-background-90 px-3 py-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-200" />
<Input
className="w-full border-none bg-transparent py-1 px-2 text-xs text-custom-text-200 focus:outline-none"
@@ -130,15 +130,15 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
{shortcut.keys.split(",").map((key, index) => (
<span key={index} className="flex items-center gap-1">
{key === "Ctrl" ? (
<span className="flex h-full items-center rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5">
<span className="flex h-full items-center rounded-sm border border-custom-border-200 bg-custom-background-90 p-1.5">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
</span>
) : key === "Ctrl" ? (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-custom-border-200 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
</kbd>
) : (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-custom-border-200 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
{key}
</kbd>
)}
@@ -173,15 +173,15 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
{keys.split(",").map((key, index) => (
<span key={index} className="flex items-center gap-1">
{key === "Ctrl" ? (
<span className="flex h-full items-center rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-custom-text-200">
<span className="flex h-full items-center rounded-sm border border-custom-border-200 bg-custom-background-90 p-1.5 text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
</span>
) : key === "Ctrl" ? (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-custom-border-200 bg-custom-background-90 p-1.5 text-sm font-medium text-custom-text-200">
<CommandIcon className="h-4 w-4 fill-current text-custom-text-200" />
</kbd>
) : (
<kbd className="rounded-sm border border-custom-border-100 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
<kbd className="rounded-sm border border-custom-border-200 bg-custom-background-90 px-2 py-1 text-sm font-medium text-custom-text-200">
{key}
</kbd>
)}
@@ -74,7 +74,7 @@ export const AllBoards: React.FC<Props> = ({
);
})}
{!showEmptyGroups && (
<div className="h-full w-96 flex-shrink-0 space-y-3 p-1">
<div className="h-full w-96 flex-shrink-0 space-y-2 p-1">
<h2 className="text-lg font-semibold">Hidden groups</h2>
<div className="space-y-3">
{Object.keys(groupedByIssues).map((singleGroup, index) => {
@@ -60,6 +60,10 @@ export const SingleBoard: React.FC<Props> = ({
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
// Check if it has at least 4 tickets since it is enough to accommodate the Calendar height
const issuesLength = groupedByIssues?.[groupTitle].length;
const hasMinimumNumberOfCards = issuesLength ? issuesLength >= 4 : false;
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted;
return (
@@ -103,7 +107,11 @@ export const SingleBoard: React.FC<Props> = ({
</div>
</>
)}
<div className="pt-3 overflow-hidden overflow-y-scroll">
<div
className={`pt-3 ${
hasMinimumNumberOfCards ? "overflow-hidden overflow-y-scroll" : ""
} `}
>
{groupedByIssues?.[groupTitle].map((issue, index) => (
<Draggable
key={issue.id}
@@ -356,7 +356,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
selfPositioned
/>
)}
{properties.due_date && (
{properties.due_date && issue.target_date && (
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -364,7 +364,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.labels && (
{properties.labels && issue.labels.length > 0 && (
<ViewLabelSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -382,7 +382,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
selfPositioned
/>
)}
{properties.estimate && (
{properties.estimate && issue.estimate_point !== null && (
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -391,8 +391,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
selfPositioned
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.sub_issue_count && issue.sub_issues_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
@@ -401,8 +401,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.link && issue.link_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Link" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LinkIcon className="h-3.5 w-3.5" />
@@ -411,8 +411,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.attachment_count && issue.attachment_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachment" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
@@ -92,7 +92,7 @@ export const CalendarHeader: React.FC<Props> = ({
</button>
))}
</div>
<div className="grid grid-cols-4 border-t border-custom-border-100 px-2">
<div className="grid grid-cols-4 border-t border-custom-border-200 px-2">
{MONTHS_LIST.map((month) => (
<button
onClick={() =>
@@ -152,7 +152,7 @@ export const CalendarHeader: React.FC<Props> = ({
<div className="flex w-full items-center justify-end gap-2">
<button
className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-100 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"
className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"
onClick={() => {
if (isMonthlyView) {
updateDate(new Date());
@@ -170,7 +170,7 @@ export const CalendarHeader: React.FC<Props> = ({
<CustomMenu
customButton={
<div className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-100 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none ">
<div className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none ">
{isMonthlyView ? "Monthly" : "Weekly"}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
@@ -207,7 +207,7 @@ export const CalendarHeader: React.FC<Props> = ({
/>
</div>
</CustomMenu.MenuItem>
<div className="mt-1 flex w-52 items-center justify-between border-t border-custom-border-100 py-2 px-1 text-sm text-custom-text-200">
<div className="mt-1 flex w-52 items-center justify-between border-t border-custom-border-200 py-2 px-1 text-sm text-custom-text-200">
<h4>Show weekends</h4>
<ToggleSwitch value={showWeekEnds} onChange={() => setShowWeekEnds(!showWeekEnds)} />
</div>
@@ -191,7 +191,7 @@ export const CalendarView: React.FC<Props> = ({
{weeks.map((date, index) => (
<div
key={index}
className={`flex items-center justify-start gap-2 border-custom-border-100 bg-custom-background-90 p-1.5 text-base font-medium text-custom-text-200 ${
className={`flex items-center justify-start gap-2 border-custom-border-200 bg-custom-background-90 p-1.5 text-base font-medium text-custom-text-200 ${
!isMonthlyView
? showWeekEnds
? (index + 1) % 7 === 0
@@ -49,7 +49,7 @@ export const SingleCalendarDate: React.FC<Props> = ({
key={index}
ref={provided.innerRef}
{...provided.droppableProps}
className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-custom-border-100 p-2.5 text-left text-sm font-medium hover:bg-custom-background-90 ${
className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-custom-border-200 p-2.5 text-left text-sm font-medium hover:bg-custom-background-90 ${
isMonthlyView ? "" : "pt-9"
} ${
showWeekEnds
@@ -83,7 +83,7 @@ export const SingleCalendarDate: React.FC<Props> = ({
{totalIssues > 4 && (
<button
type="button"
className="w-min whitespace-nowrap rounded-md border border-custom-border-100 bg-custom-background-80 px-1.5 py-1 text-xs"
className="w-min whitespace-nowrap rounded-md border border-custom-border-200 bg-custom-background-80 px-1.5 py-1 text-xs"
onClick={() => setShowAllIssues((prevData) => !prevData)}
>
{showAllIssues ? "Hide" : totalIssues - 4 + " more"}
@@ -163,7 +163,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`w-full relative cursor-pointer rounded border border-custom-border-100 px-1.5 py-1.5 text-xs duration-300 hover:cursor-move hover:bg-custom-background-80 ${
className={`w-full relative cursor-pointer rounded border border-custom-border-200 px-1.5 py-1.5 text-xs duration-300 hover:cursor-move hover:bg-custom-background-80 ${
snapshot.isDragging ? "bg-custom-background-80 shadow-lg" : ""
}`}
>
@@ -231,7 +231,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
/>
)}
{properties.due_date && (
{properties.due_date && issue.target_date && (
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -239,7 +239,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.labels && (
{properties.labels && issue.labels.length > 0 && (
<ViewLabelSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -257,7 +257,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.estimate && (
{properties.estimate && issue.estimate_point !== null && (
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -266,8 +266,8 @@ export const SingleCalendarIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.sub_issue_count && issue.sub_issues_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
@@ -276,8 +276,8 @@ export const SingleCalendarIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.link && issue.link_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LinkIcon className="h-3.5 w-3.5" />
@@ -286,8 +286,8 @@ export const SingleCalendarIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.attachment_count && issue.attachment_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
+51 -39
View File
@@ -5,19 +5,11 @@ import Link from "next/link";
// icons
import {
ArrowTopRightOnSquareIcon,
CalendarDaysIcon,
ChartBarIcon,
ChatBubbleBottomCenterTextIcon,
ChatBubbleLeftEllipsisIcon,
LinkIcon,
PaperClipIcon,
PlayIcon,
RectangleGroupIcon,
Squares2X2Icon,
TrashIcon,
UserIcon,
} from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon, CyclesIcon, TagIcon, UserGroupIcon } from "components/icons";
import { BlockedIcon, BlockerIcon } from "components/icons";
import { Icon } from "components/ui";
// helpers
import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
@@ -32,11 +24,11 @@ const activityDetails: {
} = {
assignee: {
message: "removed the assignee",
icon: <UserGroupIcon className="h-3 w-3" color="#6b7280" aria-hidden="true" />,
icon: <Icon iconName="group" className="!text-sm" aria-hidden="true" />,
},
assignees: {
message: "added a new assignee",
icon: <UserGroupIcon className="h-3 w-3" color="#6b7280" aria-hidden="true" />,
icon: <Icon iconName="group" className="!text-sm" aria-hidden="true" />,
},
blocks: {
message: "marked this issue being blocked by",
@@ -48,14 +40,14 @@ const activityDetails: {
},
cycles: {
message: "set the cycle to",
icon: <CyclesIcon height="12" width="12" color="#6b7280" />,
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
},
labels: {
icon: <TagIcon height="12" width="12" color="#6b7280" />,
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
},
modules: {
message: "set the module to",
icon: <RectangleGroupIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
},
state: {
message: "set the state to",
@@ -63,47 +55,47 @@ const activityDetails: {
},
priority: {
message: "set the priority to",
icon: <ChartBarIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
},
name: {
message: "set the name to",
icon: (
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />
),
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
},
description: {
message: "updated the description.",
icon: (
<ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />
),
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
},
estimate_point: {
message: "set the estimate point to",
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="change_history" className="!text-sm" aria-hidden="true" />,
},
target_date: {
message: "set the due date to",
icon: <CalendarDaysIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
},
parent: {
message: "set the parent to",
icon: <UserIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
},
issue: {
message: "deleted the issue.",
icon: <TrashIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="delete" className="!text-sm" aria-hidden="true" />,
},
estimate: {
message: "updated the estimate",
icon: <PlayIcon className="h-3 w-3 -rotate-90 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="change_history" className="!text-sm" aria-hidden="true" />,
},
link: {
message: "updated the link",
icon: <LinkIcon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
icon: <Icon iconName="link" className="!text-sm" aria-hidden="true" />,
},
attachment: {
message: "updated the attachment",
icon: <PaperClipIcon className="h-3 w-3 text-custom-text-200 " aria-hidden="true" />,
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
},
archived_at: {
message: "archived",
icon: <Icon iconName="archive" className="!text-sm text-custom-text-200" aria-hidden="true" />,
},
};
@@ -144,6 +136,11 @@ export const Feeds: React.FC<any> = ({ activities }) => (
action = `${activity.verb} the`;
} else if (activity.field === "link") {
action = `${activity.verb} the`;
} else if (activity.field === "archived_at") {
action =
activity.new_value && activity.new_value === "restore"
? "restored the issue"
: "archived the issue";
}
// for values that are after the action clause
let value: any = activity.new_value ? activity.new_value : activity.old_value;
@@ -205,7 +202,13 @@ export const Feeds: React.FC<any> = ({ activities }) => (
<div key={activity.id} className="mt-2">
<div className="relative flex items-start space-x-3">
<div className="relative px-1">
{activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
{activity.field ? (
activity.new_value === "restore" ? (
<Icon iconName="history" className="text-sm text-custom-text-200" />
) : (
activityDetails[activity.field as keyof typeof activityDetails]?.icon
)
) : activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
<img
src={activity.actor_detail.avatar}
alt={activity.actor_detail.first_name}
@@ -247,7 +250,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
}
editable={false}
noBorder
customClassName="text-xs border border-custom-border-100 bg-custom-background-100"
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
/>
</div>
</div>
@@ -271,7 +274,7 @@ export const Feeds: React.FC<any> = ({ activities }) => (
<div>
<div className="relative px-1.5">
<div className="mt-1.5">
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 ring-white">
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
{activity.field ? (
activityDetails[activity.field as keyof typeof activityDetails]?.icon
) : activity.actor_detail.avatar &&
@@ -296,14 +299,23 @@ export const Feeds: React.FC<any> = ({ activities }) => (
</div>
<div className="min-w-0 flex-1 py-3">
<div className="text-xs text-custom-text-200">
<span className="text-gray font-medium">
{activity.actor_detail.first_name}
{activity.actor_detail.is_bot
? " Bot"
: " " + activity.actor_detail.last_name}
</span>
{activity.field === "archived_at" && activity.new_value !== "restore" ? (
<span className="text-gray font-medium">Plane</span>
) : (
<span className="text-gray font-medium">
{activity.actor_detail.first_name}
{activity.actor_detail.is_bot
? " Bot"
: " " + activity.actor_detail.last_name}
</span>
)}
<span> {action} </span>
<span className="text-xs font-medium text-custom-text-100"> {value} </span>
{activity.field !== "archived_at" && (
<span className="text-xs font-medium text-custom-text-100">
{" "}
{value}{" "}
</span>
)}
<span className="whitespace-nowrap">{timeAgo(activity.created_at)}</span>
</div>
</div>
@@ -109,7 +109,7 @@ export const DueDateFilterModal: React.FC<Props> = ({ isOpen, handleClose }) =>
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative flex transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form className="space-y-4" onSubmit={handleSubmit(handleFormSubmit)}>
<div className="flex w-full justify-between">
<Controller
@@ -58,7 +58,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
return (
<div
key={key}
className="flex items-center gap-x-2 rounded-full border border-custom-border-100 bg-custom-background-80 px-2 py-1"
className="flex items-center gap-x-2 rounded-full border border-custom-border-200 bg-custom-background-80 px-2 py-1"
>
<span className="capitalize text-custom-text-200">
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
@@ -310,7 +310,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
return (
<div
key={date}
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-100 bg-custom-background-100 px-1 py-0.5"
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-100 px-1 py-0.5"
>
<div className="h-1.5 w-1.5 rounded-full" />
<span className="capitalize">
@@ -381,7 +381,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
target_date: null,
})
}
className="flex items-center gap-x-1 rounded-full border border-custom-border-100 bg-custom-background-80 px-3 py-1.5 text-xs"
className="flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-80 px-3 py-1.5 text-xs"
>
<span>Clear all filters</span>
<XMarkIcon className="h-3 w-3" />
@@ -11,14 +11,16 @@ import useEstimateOption from "hooks/use-estimate-option";
// components
import { SelectFilters } from "components/views";
// ui
import { CustomMenu, Icon, ToggleSwitch } from "components/ui";
import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import {
ChevronDownIcon,
ListBulletIcon,
Squares2X2Icon,
CalendarDaysIcon,
} from "@heroicons/react/24/outline";
CalendarMonthOutlined,
FormatListBulletedOutlined,
GridViewOutlined,
TableChartOutlined,
WaterfallChartOutlined,
} from "@mui/icons-material";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
@@ -27,32 +29,33 @@ import { Properties, TIssueViewOptions } from "types";
// constants
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
const issueViewOptions: { type: TIssueViewOptions; icon: any }[] = [
const issueViewOptions: { type: TIssueViewOptions; Icon: any }[] = [
{
type: "list",
icon: <ListBulletIcon className="h-4 w-4" />,
Icon: FormatListBulletedOutlined,
},
{
type: "kanban",
icon: <Squares2X2Icon className="h-4 w-4" />,
Icon: GridViewOutlined,
},
{
type: "calendar",
icon: <CalendarDaysIcon className="h-4 w-4" />,
Icon: CalendarMonthOutlined,
},
{
type: "spreadsheet",
icon: <Icon iconName="table_chart" />,
Icon: TableChartOutlined,
},
{
type: "gantt_chart",
icon: <Icon iconName="waterfall_chart" className="rotate-90" />,
Icon: WaterfallChartOutlined,
},
];
export const IssuesFilterView: React.FC = () => {
const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues");
const {
issueView,
@@ -78,22 +81,36 @@ export const IssuesFilterView: React.FC = () => {
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-x-1">
{issueViewOptions.map((option) => (
<button
key={option.type}
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type
? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200"
}`}
onClick={() => setIssueView(option.type)}
>
{option.icon}
</button>
))}
</div>
{!isArchivedIssues && (
<div className="flex items-center gap-x-1">
{issueViewOptions.map((option) => (
<Tooltip
key={option.type}
tooltipContent={
<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} View</span>
}
position="bottom"
>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type
? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200"
}`}
onClick={() => setIssueView(option.type)}
>
<option.Icon
sx={{
fontSize: 16,
}}
className={option.type === "gantt_chart" ? "rotate-90" : ""}
/>
</button>
</Tooltip>
))}
</div>
)}
<SelectFilters
filters={filters}
onSelect={(option) => {
@@ -136,7 +153,7 @@ export const IssuesFilterView: React.FC = () => {
{({ open }) => (
<>
<Popover.Button
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-100 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
open
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
: "text-custom-sidebar-text-200"
@@ -155,8 +172,8 @@ export const IssuesFilterView: React.FC = () => {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-100 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-100">
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-200">
<div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && issueView !== "spreadsheet" && (
<>
@@ -167,7 +184,6 @@ export const IssuesFilterView: React.FC = () => {
GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
?.name ?? "Select"
}
width="lg"
>
{GROUP_BY_OPTIONS.map((option) =>
issueView === "kanban" && option.key === null ? null : (
@@ -188,7 +204,6 @@ export const IssuesFilterView: React.FC = () => {
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
"Select"
}
width="lg"
>
{ORDER_BY_OPTIONS.map((option) =>
groupByProperty === "priority" && option.key === "priority" ? null : (
@@ -213,7 +228,6 @@ export const IssuesFilterView: React.FC = () => {
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type)
?.name ?? "Select"
}
width="lg"
>
{FILTER_ISSUE_OPTIONS.map((option) => (
<CustomMenu.MenuItem
@@ -282,7 +296,7 @@ export const IssuesFilterView: React.FC = () => {
className={`rounded border px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties]
? "border-custom-primary bg-custom-primary text-white"
: "border-custom-border-100"
: "border-custom-border-200"
}`}
onClick={() => setProperties(key as keyof Properties)}
>
@@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
return (
<Popover className="relative z-[2]" ref={ref}>
<Popover.Button
className="rounded-md border border-custom-border-100 bg-custom-background-80 px-2 py-1 text-xs text-custom-text-200"
className="rounded-md border border-custom-border-200 bg-custom-background-80 px-2 py-1 text-xs text-custom-text-200"
onClick={() => setIsOpen((prev) => !prev)}
>
{label}
@@ -76,8 +76,8 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-custom-border-100 bg-custom-background-80 shadow-lg">
<div className="h-96 w-80 overflow-auto rounded border border-custom-border-100 bg-custom-background-80 p-5 shadow-2xl sm:max-w-2xl md:w-96 lg:w-[40rem]">
<Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-custom-border-200 bg-custom-background-80 shadow-lg">
<div className="h-96 w-80 overflow-auto rounded border border-custom-border-200 bg-custom-background-80 p-5 shadow-2xl sm:max-w-2xl md:w-96 lg:w-[40rem]">
<Tab.Group>
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
{tabOptions.map((tab) => (
+20 -3
View File
@@ -31,11 +31,12 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { CreateUpdateViewModal } from "components/views";
import { TransferIssues, TransferIssuesModal } from "components/cycles";
// ui
import { EmptyState, PrimaryButton, Spinner } from "components/ui";
import { EmptyState, PrimaryButton, Spinner, Icon } from "components/ui";
// icons
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
// images
import emptyIssue from "public/empty-state/issue.svg";
import emptyIssueArchive from "public/empty-state/issue-archive.svg";
// helpers
import { getStatesList } from "helpers/state.helper";
import { orderArrayBy } from "helpers/array.helper";
@@ -488,7 +489,7 @@ export const IssuesView: React.FC<Props> = ({
{viewId ? "Update" : "Save"} view
</PrimaryButton>
</div>
{<div className="mt-3 border-t border-custom-border-100" />}
{<div className="mt-3 border-t border-custom-border-200" />}
</>
)}
@@ -577,9 +578,25 @@ export const IssuesView: React.FC<Props> = ({
issueView === "gantt_chart" && <GanttChartView />
)}
</>
) : router.pathname.includes("archived-issues") ? (
<EmptyState
title="Archived Issues will be shown here"
description="All the issues that have been in the completed or canceled groups for the configured period of time can be viewed here."
image={emptyIssueArchive}
buttonText="Go to Automation Settings"
onClick={() => {
router.push(`/${workspaceSlug}/projects/${projectId}/settings/automations`);
}}
/>
) : (
<EmptyState
title="Project issues will appear here"
title={
cycleId
? "Cycle issues will appear here"
: moduleId
? "Module issues will appear here"
: "Project issues will appear here"
}
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
image={emptyIssue}
buttonText="New Issue"
@@ -84,6 +84,7 @@ export const SingleListIssue: React.FC<Props> = ({
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues");
const { setToastAlert } = useToast();
@@ -181,7 +182,11 @@ export const SingleListIssue: React.FC<Props> = ({
});
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted;
const singleIssuePath = isArchivedIssues
? `/${workspaceSlug}/projects/${projectId}/archived-issues/${issue.id}`
: `/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`;
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted || isArchivedIssues;
return (
<>
@@ -207,25 +212,21 @@ export const SingleListIssue: React.FC<Props> = ({
<ContextMenu.Item Icon={LinkIcon} onClick={handleCopyText}>
Copy issue link
</ContextMenu.Item>
<a
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
target="_blank"
rel="noreferrer noopener"
>
<a href={singleIssuePath} target="_blank" rel="noreferrer noopener">
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
Open issue in new tab
</ContextMenu.Item>
</a>
</ContextMenu>
<div
className="flex flex-wrap items-center justify-between px-4 py-2.5 gap-2 border-b border-custom-border-100 bg-custom-background-100 last:border-b-0"
className="flex flex-wrap items-center justify-between px-4 py-2.5 gap-2 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
setContextMenuPosition({ x: e.pageX, y: e.pageY });
}}
>
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
<Link href={singleIssuePath}>
<div className="flex-grow cursor-pointer">
<a className="group relative flex items-center gap-2">
{properties.key && (
@@ -247,7 +248,11 @@ export const SingleListIssue: React.FC<Props> = ({
</div>
</Link>
<div className="flex w-full flex-shrink flex-wrap items-center gap-2 text-xs sm:w-auto">
<div
className={`flex w-full flex-shrink flex-wrap items-center gap-2 text-xs sm:w-auto ${
isArchivedIssues ? "opacity-60" : ""
}`}
>
{properties.priority && (
<ViewPrioritySelect
issue={issue}
@@ -266,7 +271,7 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.due_date && (
{properties.due_date && issue.target_date && (
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -274,7 +279,7 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.labels && (
{properties.labels && issue.labels.length > 0 && (
<ViewLabelSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -292,7 +297,7 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.estimate && (
{properties.estimate && issue.estimate_point !== null && (
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -301,8 +306,8 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed}
/>
)}
{properties.sub_issue_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.sub_issue_count && issue.sub_issues_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Sub-issue" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LayerDiagonalIcon className="h-3.5 w-3.5" />
@@ -311,8 +316,8 @@ export const SingleListIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.link && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.link && issue.link_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<LinkIcon className="h-3.5 w-3.5" />
@@ -321,8 +326,8 @@ export const SingleListIssue: React.FC<Props> = ({
</Tooltip>
</div>
)}
{properties.attachment_count && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-100 px-2.5 py-1 text-xs shadow-sm">
{properties.attachment_count && issue.attachment_count > 0 && (
<div className="flex cursor-default items-center rounded-md border border-custom-border-200 px-2.5 py-1 text-xs shadow-sm">
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex items-center gap-1 text-custom-text-200">
<PaperClipIcon className="h-3.5 w-3.5 -rotate-45" />
@@ -67,6 +67,7 @@ export const SingleList: React.FC<Props> = ({
}) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues");
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
@@ -159,7 +160,9 @@ export const SingleList: React.FC<Props> = ({
</span>
</div>
</Disclosure.Button>
{type === "issue" ? (
{isArchivedIssues ? (
""
) : type === "issue" ? (
<button
type="button"
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
@@ -173,7 +173,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<form>
<Combobox
onChange={(val: string) => {
@@ -201,7 +201,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
<Combobox.Options
static
className="max-h-80 scroll-py-2 divide-y divide-custom-border-100 overflow-y-auto"
className="max-h-80 scroll-py-2 divide-y divide-custom-border-200 overflow-y-auto"
>
{filteredIssues.length > 0 ? (
<li className="p-2">
@@ -154,7 +154,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-100 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<Combobox
as="div"
onChange={(val: ISearchIssueResponse) => {
@@ -182,7 +182,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
{selectedIssues.map((issue) => (
<div
key={issue.id}
className="flex items-center gap-1 text-xs border border-custom-border-100 bg-custom-background-80 pl-2 py-1 rounded-md text-custom-text-100 whitespace-nowrap"
className="flex items-center gap-1 text-xs border border-custom-border-200 bg-custom-background-80 pl-2 py-1 rounded-md text-custom-text-100 whitespace-nowrap"
>
{issue.project__identifier}-{issue.sequence_id}
<button
@@ -200,7 +200,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
))}
</div>
) : (
<div className="w-min text-xs border border-custom-border-100 bg-custom-background-80 p-2 rounded-md whitespace-nowrap">
<div className="w-min text-xs border border-custom-border-200 bg-custom-background-80 p-2 rounded-md whitespace-nowrap">
No issues selected
</div>
)}
@@ -143,7 +143,7 @@ export const GptAssistantModal: React.FC<Props> = ({
return (
<div
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-100 bg-custom-background-100 p-4 shadow ${
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${
isOpen ? "block" : "hidden"
}`}
>
@@ -118,7 +118,7 @@ export const ImageUploadModal: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<div className="space-y-5">
<Dialog.Title
as="h3"
@@ -132,7 +132,7 @@ export const ImageUploadModal: React.FC<Props> = ({
{...getRootProps()}
className={`relative grid h-80 w-full cursor-pointer place-items-center rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-custom-primary focus:ring-offset-2 ${
(image === null && isDragActive) || !value
? "border-2 border-dashed border-custom-border-100 hover:bg-custom-background-90"
? "border-2 border-dashed border-custom-border-200 hover:bg-custom-background-90"
: ""
}`}
>
@@ -56,7 +56,7 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@@ -70,7 +70,7 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-200 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<div className="space-y-5">
@@ -49,7 +49,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
)}
<Link href={link.url}>
<a
className="relative flex gap-2 rounded-md bg-custom-background-100 p-2"
className="relative flex gap-2 rounded-md bg-custom-background-90 p-2"
target="_blank"
>
<div className="mt-0.5">
@@ -119,7 +119,7 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))}
enableSlices="x"
sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-100 bg-custom-background-80 p-2 text-xs">
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
{datum.slice.points[0].data.yFormatted}
<span className="text-custom-text-200"> issues pending on </span>
{datum.slice.points[0].data.xFormatted}
@@ -87,7 +87,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
roundedTab ? "rounded-3xl border border-custom-border-200" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
}`
@@ -98,7 +98,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
roundedTab ? "rounded-3xl border border-custom-border-200" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
}`
@@ -109,7 +109,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
<Tab
className={({ selected }) =>
`w-full ${
roundedTab ? "rounded-3xl border border-custom-border-100" : "rounded"
roundedTab ? "rounded-3xl border border-custom-border-200" : "rounded"
} px-3 py-1 text-custom-text-100 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
}`
@@ -159,7 +159,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-100 bg-custom-background-80">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img
src="/user.png"
height="100%"
@@ -179,10 +179,10 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
return (
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-100 w-full min-w-max"
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max"
style={{ gridTemplateColumns }}
>
<div className="flex gap-1.5 items-center px-4 sticky z-[1] left-0 text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full">
<div className="flex gap-1.5 items-center px-4 sticky z-[1] left-0 text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full">
<div className="flex gap-1.5 items-center" style={issue.parent ? { paddingLeft } : {}}>
<div className="relative flex items-center cursor-pointer text-xs text-center hover:text-custom-text-100 w-14">
{properties.key && (
@@ -198,7 +198,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
onInteraction={(nextOpenState) => setIsOpen(nextOpenState)}
content={
<div
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-custom-border-100 bg-custom-background-90`}
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-custom-border-200 bg-custom-background-90`}
>
<button
type="button"
@@ -270,7 +270,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</Link>
</div>
{properties.state && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewStateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -284,7 +284,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.priority && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewPrioritySelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -297,7 +297,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.assignee && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewAssigneeSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -310,7 +310,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.labels && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewLabelSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -324,7 +324,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
)}
{properties.due_date && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -336,7 +336,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.estimate && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-100">
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
@@ -348,12 +348,12 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
</div>
)}
{properties.created_on && (
<div className="flex items-center text-xs cursor-default text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
{renderLongDetailDateFormat(issue.created_at)}
</div>
)}
{properties.updated_on && (
<div className="flex items-center text-xs cursor-default text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
{renderLongDetailDateFormat(issue.updated_at)}
</div>
)}
@@ -56,7 +56,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
className="!w-full"
customButton={
<div
className={`relative group flex items-center justify-start gap-1.5 cursor-pointer text-sm text-custom-text-200 text-current hover:text-custom-text-100 w-full py-3 px-2 ${
className={`relative group flex items-center justify-start gap-1.5 cursor-pointer text-sm text-custom-text-200 hover:text-custom-text-100 w-full py-3 px-2 ${
activeSortingProperty === col.propertyName ? "bg-custom-background-80" : ""
}`}
>
@@ -90,16 +90,9 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
}
menuItemsWhiteBg
width="xl"
>
<CustomMenu.MenuItem
className={`${
selectedMenuItem === `${col.ascendingOrder}_${col.propertyName}`
? "bg-custom-background-80"
: ""
}`}
key={col.propertyName}
onClick={() => {
handleOrderBy(col.ascendingOrder, col.propertyName);
}}
@@ -62,7 +62,7 @@ export const SpreadsheetView: React.FC<Props> = ({
return (
<div className="h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
<div className="sticky z-[2] top-0 border-b border-custom-border-100 bg-custom-background-90 w-full min-w-max">
<div className="sticky z-[2] top-0 border-b border-custom-border-200 bg-custom-background-90 w-full min-w-max">
<SpreadsheetColumns columnData={columnData} gridTemplateColumns={gridTemplateColumns} />
</div>
{spreadsheetIssues ? (
@@ -84,12 +84,12 @@ export const SpreadsheetView: React.FC<Props> = ({
/>
))}
<div
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-100 w-full min-w-max"
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max"
style={{ gridTemplateColumns }}
>
{type === "issue" ? (
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full"
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "c" });
document.dispatchEvent(e);
@@ -104,7 +104,7 @@ export const SpreadsheetView: React.FC<Props> = ({
className="sticky left-0 z-[1]"
customButton={
<button
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-100 w-full"
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full"
type="button"
>
<PlusIcon className="h-4 w-4" />
@@ -112,7 +112,7 @@ export const SpreadsheetView: React.FC<Props> = ({
</button>
}
position="left"
menuItemsClassName="left-5 !w-36"
optionsClassName="left-5 !w-36"
noBorder
>
<CustomMenu.MenuItem
@@ -81,7 +81,7 @@ export const ColorPickerInput: React.FC<Props> = ({ name, watch, setValue, error
>
{watch(name) && watch(name) !== "" ? (
<span
className="h-4 w-4 rounded border border-custom-border-100"
className="h-4 w-4 rounded border border-custom-border-200"
style={{
backgroundColor: `${watch(name)}`,
}}
@@ -21,11 +21,11 @@ type Props = {
};
const defaultValues: ICustomTheme = {
background: "#fff7f7",
text: "#ffc9c9",
primary: "#fe5050",
sidebarBackground: "#ffffff",
sidebarText: "#000000",
background: "#0d101b",
text: "#c5c5c5",
primary: "#3f76ff",
sidebarBackground: "#0d101b",
sidebarText: "#c5c5c5",
darkPalette: false,
palette: "",
};
@@ -69,8 +69,8 @@ export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
return { ...prevData, ...res };
}, false);
applyTheme(payload.palette, darkPalette);
setTheme("custom");
applyTheme(payload.palette, darkPalette);
})
.catch((err) => console.log(err));
};
@@ -72,8 +72,21 @@ export const ThemeSwitch: React.FC<Props> = ({
onChange={({ value, type }: { value: string; type: string }) => {
if (value === "custom") {
if (user?.theme.palette) {
setPreLoadedData(user.theme);
setPreLoadedData({
background: user.theme.background !== "" ? user.theme.background : "#0d101b",
text: user.theme.text !== "" ? user.theme.text : "#c5c5c5",
primary: user.theme.primary !== "" ? user.theme.primary : "#3f76ff",
sidebarBackground:
user.theme.sidebarBackground !== "" ? user.theme.sidebarBackground : "#0d101b",
sidebarText: user.theme.sidebarText !== "" ? user.theme.sidebarText : "#c5c5c5",
darkPalette: false,
palette:
user.theme.palette !== ",,,,"
? user.theme.palette
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
});
}
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
} else {
if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false);
@@ -81,11 +94,14 @@ export const ThemeSwitch: React.FC<Props> = ({
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
document.documentElement.style.removeProperty(`--color-background-${i}`);
document.documentElement.style.removeProperty(`--color-text-${i}`);
document.documentElement.style.removeProperty(`--color-border-${i}`);
document.documentElement.style.removeProperty(`--color-primary-${i}`);
document.documentElement.style.removeProperty(`--color-sidebar-background-${i}`);
document.documentElement.style.removeProperty(`--color-sidebar-text-${i}`);
document.documentElement.style.removeProperty(`--color-sidebar-border-${i}`);
}
}
setTheme(value);
document.documentElement.style.setProperty("color-scheme", type);
}}
@@ -209,8 +209,8 @@ export const ActiveCycleDetails: React.FC = () => {
}));
return (
<div className="grid-row-2 grid rounded-[10px] shadow divide-y bg-custom-background-100 border border-custom-border-100">
<div className="grid grid-cols-1 divide-y border-custom-border-100 lg:divide-y-0 lg:divide-x lg:grid-cols-3">
<div className="grid-row-2 grid rounded-[10px] shadow divide-y bg-custom-background-100 border border-custom-border-200">
<div className="grid grid-cols-1 divide-y border-custom-border-200 lg:divide-y-0 lg:divide-x lg:grid-cols-3">
<div className="flex flex-col text-xs">
<div className="h-full w-full">
<div className="flex h-60 flex-col gap-5 justify-between rounded-b-[10px] p-4">
@@ -362,8 +362,8 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
</div>
</div>
<div className="grid col-span-2 grid-cols-1 divide-y border-custom-border-100 md:divide-y-0 md:divide-x md:grid-cols-2">
<div className="flex h-60 flex-col border-custom-border-100">
<div className="grid col-span-2 grid-cols-1 divide-y border-custom-border-200 md:divide-y-0 md:divide-x md:grid-cols-2">
<div className="flex h-60 flex-col border-custom-border-200">
<div className="flex h-full w-full flex-col text-custom-text-200 p-4">
<div className="flex w-full items-center gap-2 py-1">
<span>Progress</span>
@@ -391,12 +391,12 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
</div>
</div>
<div className="border-custom-border-100 h-60 overflow-y-scroll">
<div className="border-custom-border-200 h-60 overflow-y-scroll">
<ActiveCycleProgressStats cycle={cycle} />
</div>
</div>
</div>
<div className="grid grid-cols-1 divide-y border-custom-border-100 lg:divide-y-0 lg:divide-x lg:grid-cols-2">
<div className="grid grid-cols-1 divide-y border-custom-border-200 lg:divide-y-0 lg:divide-x lg:grid-cols-2">
<div className="flex flex-col justify-between p-4">
<div>
<div className="text-custom-primary">High Priority Issues</div>
@@ -406,7 +406,7 @@ export const ActiveCycleDetails: React.FC = () => {
issues.map((issue) => (
<div
key={issue.id}
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-custom-border-100 bg-custom-background-90 px-3 py-1.5"
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5"
>
<div className="flex flex-col gap-1">
<div>
@@ -444,7 +444,7 @@ export const ActiveCycleDetails: React.FC = () => {
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs text-custom-text-200"
className="group flex items-center gap-1 rounded-2xl border border-custom-border-200 px-2 py-0.5 text-xs text-custom-text-200"
>
<span
className="h-1.5 w-1.5 rounded-full"
@@ -517,7 +517,7 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
)}
</div>
<div className="flex flex-col justify-between border-custom-border-100 p-4">
<div className="flex flex-col justify-between border-custom-border-200 p-4">
<div className="flex items-start justify-between gap-4 py-1.5 text-xs">
<div className="flex items-center gap-3 text-custom-text-100">
<div className="flex items-center justify-center gap-1">
@@ -52,7 +52,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
>
<Tab
className={({ selected }) =>
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-100 ${
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-200 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
}`
}
@@ -61,7 +61,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
</Tab>
<Tab
className={({ selected }) =>
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-100 ${
`px-3 py-1 text-custom-text-100 rounded-3xl border border-custom-border-200 ${
selected ? " bg-custom-primary text-white" : " hover:bg-custom-background-80"
}`
}
@@ -103,7 +103,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-100 bg-custom-background-80">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img
src="/user.png"
height="100%"
@@ -22,7 +22,7 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles }) => {
<div className="relative flex w-full h-full items-center p-1 overflow-hidden gap-1">
<div
className="rounded-sm flex-shrink-0 w-[10px] h-[10px] flex justify-center items-center"
style={{ backgroundColor: "#858e96" }}
style={{ backgroundColor: "rgb(var(--color-primary-100))" }}
/>
<div className="text-custom-text-100 text-sm">{data?.name}</div>
</div>
@@ -32,7 +32,10 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles }) => {
const GanttBlockView = ({ data }: { data: ICycle }) => (
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${data?.id}`}>
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
<div className="flex-shrink-0 w-[4px] h-full" style={{ backgroundColor: "#858e96" }} />
<div
className="flex-shrink-0 w-[4px] h-full"
style={{ backgroundColor: "rgb(var(--color-primary-100))" }}
/>
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
{data?.name}
+2 -2
View File
@@ -173,10 +173,10 @@ export const CyclesView: React.FC<Props> = ({ cycles, viewType }) => {
{cycles ? (
cycles.length > 0 ? (
viewType === "list" ? (
<div className="divide-y divide-custom-border-100">
<div className="divide-y divide-custom-border-200">
{cycles.map((cycle) => (
<div className="hover:bg-custom-background-80">
<div className="flex flex-col border-custom-border-100">
<div className="flex flex-col border-custom-border-200">
<SingleCycleList
key={cycle.id}
cycle={cycle}
@@ -124,7 +124,7 @@ export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
+1 -1
View File
@@ -107,7 +107,7 @@ export const CycleForm: React.FC<Props> = ({ handleFormSubmit, handleClose, stat
</div>
</div>
</div>
<div className="-mx-5 mt-5 flex justify-end gap-2 border-t border-custom-border-100 px-5 pt-5">
<div className="-mx-5 mt-5 flex justify-end gap-2 border-t border-custom-border-200 px-5 pt-5">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{status
+2 -2
View File
@@ -26,7 +26,7 @@ export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
<div className="relative flex w-full h-full items-center p-1 overflow-hidden gap-1">
<div
className="rounded-sm flex-shrink-0 w-[10px] h-[10px] flex justify-center items-center"
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
/>
<div className="text-custom-text-100 text-sm">{data?.name}</div>
</div>
@@ -38,7 +38,7 @@ export const CycleIssuesGanttChartView: FC<Props> = ({}) => {
<a className="relative flex items-center w-full h-full overflow-hidden shadow-sm">
<div
className="flex-shrink-0 w-[4px] h-full"
style={{ backgroundColor: data?.state_detail?.color || "#858e96" }}
style={{ backgroundColor: data?.state_detail?.color || "rgb(var(--color-primary-100))" }}
/>
<Tooltip tooltipContent={data?.name} className={`z-[999999]`}>
<div className="text-custom-text-100 text-[15px] whitespace-nowrap py-[4px] px-2.5 overflow-hidden w-full">
+1 -1
View File
@@ -247,7 +247,7 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<CycleForm
handleFormSubmit={handleFormSubmit}
handleClose={handleClose}
+1 -1
View File
@@ -66,7 +66,7 @@ export const CycleSelect: React.FC<IssueCycleSelectProps> = ({
{({ open }) => (
<>
<Listbox.Button
className={`flex cursor-pointer items-center gap-1 rounded-md border border-custom-border-100 px-2 py-1 text-xs shadow-sm duration-300 hover:bg-custom-background-90 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500`}
className={`flex cursor-pointer items-center gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 hover:bg-custom-background-90 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500`}
>
<CyclesIcon className="h-3 w-3 text-custom-text-200" />
<div className="flex items-center gap-2 truncate">
+6 -6
View File
@@ -292,14 +292,14 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
<div
className={`fixed top-[66px] ${
isOpen ? "right-0" : "-right-[24rem]"
} h-full w-[24rem] overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 pt-5 pb-10 duration-300`}
} h-full w-[24rem] overflow-y-auto border-l border-custom-border-200 bg-custom-sidebar-background-100 pt-5 pb-10 duration-300`}
>
{cycle ? (
<>
<div className="flex flex-col items-start justify-center">
<div className="flex gap-2.5 px-5 text-sm">
<div className="flex items-center">
<span className="flex items-center rounded border-[0.5px] border-custom-border-100 bg-custom-background-90 px-2 py-1 text-center text-xs capitalize">
<span className="flex items-center rounded border-[0.5px] border-custom-border-200 bg-custom-background-90 px-2 py-1 text-center text-xs capitalize">
{capitalizeFirstLetter(cycleStatus)}
</span>
</div>
@@ -309,7 +309,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
<>
<Popover.Button
disabled={isCompleted ?? false}
className={`group flex h-full items-center gap-2 whitespace-nowrap rounded border-[0.5px] border-custom-border-100 bg-custom-background-90 px-2 py-1 text-xs ${
className={`group flex h-full items-center gap-2 whitespace-nowrap rounded border-[0.5px] border-custom-border-200 bg-custom-background-90 px-2 py-1 text-xs ${
cycle.start_date ? "" : "text-custom-text-200"
}`}
>
@@ -359,7 +359,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
<>
<Popover.Button
disabled={isCompleted ?? false}
className={`group flex items-center gap-2 whitespace-nowrap rounded border-[0.5px] border-custom-border-100 bg-custom-background-90 px-2 py-1 text-xs ${
className={`group flex items-center gap-2 whitespace-nowrap rounded border-[0.5px] border-custom-border-200 bg-custom-background-90 px-2 py-1 text-xs ${
cycle.end_date ? "" : "text-custom-text-200"
}`}
>
@@ -477,7 +477,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
</div>
</div>
</div>
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-100 p-6">
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-200 p-6">
<Disclosure defaultOpen>
{({ open }) => (
<div
@@ -561,7 +561,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
)}
</Disclosure>
</div>
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-100 p-6">
<div className="flex w-full flex-col items-center justify-start gap-2 border-t border-custom-border-200 p-6">
<Disclosure defaultOpen>
{({ open }) => (
<div
@@ -128,7 +128,7 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
return (
<div>
<div className="flex flex-col rounded-[10px] bg-custom-background-100 border border-custom-border-100 text-xs shadow">
<div className="flex flex-col rounded-[10px] bg-custom-background-100 border border-custom-border-200 text-xs shadow">
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
<a className="w-full">
<div className="flex h-full flex-col gap-4 rounded-b-[10px] p-4">
@@ -321,7 +321,7 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = ({
<Disclosure>
{({ open }) => (
<div
className={`flex h-full w-full flex-col rounded-b-[10px] border-t border-custom-border-100 bg-custom-background-80 text-custom-text-200 ${
className={`flex h-full w-full flex-col rounded-b-[10px] border-t border-custom-border-200 bg-custom-background-80 text-custom-text-200 ${
open ? "" : "flex-row"
}`}
>
@@ -119,7 +119,7 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
<XMarkIcon className="h-4 w-4" />
</button>
</div>
<div className="flex items-center gap-2 border-b border-custom-border-100 px-5 pb-3">
<div className="flex items-center gap-2 border-b border-custom-border-200 px-5 pb-3">
<MagnifyingGlassIcon className="h-4 w-4 text-custom-text-200" />
<input
className="bg-custom-background-90 outline-none"
@@ -63,8 +63,8 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Popover.Panel className="absolute z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-100 bg-custom-background-80 shadow-lg">
<div className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-100 bg-custom-background-80 p-2 shadow-xl">
<Popover.Panel className="absolute z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg">
<div className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl">
<Tab.Group as="div" className="flex h-full w-full flex-col">
<Tab.List className="flex-0 -mx-2 flex justify-around gap-1 p-1">
{tabOptions.map((tab) => (
@@ -107,7 +107,7 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
</div>
</div>
)}
<hr className="mb-2 h-[1px] w-full border-custom-border-100" />
<hr className="mb-2 h-[1px] w-full border-custom-border-200" />
<div>
<div className="grid grid-cols-8 gap-x-2 gap-y-3">
{emojis.map((emoji) => (
@@ -173,7 +173,7 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
/>
</div>
</div>
<hr className="mb-1 h-[1px] w-full border-custom-border-100" />
<hr className="mb-1 h-[1px] w-full border-custom-border-200" />
<div className="mt-1 ml-1 grid grid-cols-8 gap-x-2 gap-y-3">
{icons.material_rounded.map((icon, index) => (
<button
@@ -254,7 +254,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-100 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-3">
<div className="text-lg font-medium leading-6">
@@ -60,7 +60,7 @@ export const DeleteEstimateModal: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@@ -44,7 +44,7 @@ export const GanttChartBlocks: FC<{
</div>
<div
className="rounded shadow-sm bg-custom-background-100 overflow-hidden relative flex items-center h-[34px] border border-custom-border-100"
className="rounded shadow-sm bg-custom-background-100 overflow-hidden relative flex items-center h-[34px] border border-custom-border-200"
style={{
width: `${block?.position?.width}px`,
}}
@@ -68,7 +68,7 @@ export const GanttChartBlocks: FC<{
</div>
{/* sidebar */}
{/* <div className="fixed top-0 bottom-0 w-[300px] flex-shrink-0 divide-y divide-custom-border-100 border-r border-custom-border-100 overflow-y-auto">
{/* <div className="fixed top-0 bottom-0 w-[300px] flex-shrink-0 divide-y divide-custom-border-200 border-r border-custom-border-200 overflow-y-auto">
{blocks &&
blocks.length > 0 &&
blocks.map((block: any, _idx: number) => (
@@ -7,18 +7,18 @@ export const BiWeekChartView: FC<any> = () => {
return (
<>
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-100">
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-200">
{renderView &&
renderView.length > 0 &&
renderView.map((_itemRoot: any, _idxRoot: any) => (
<div key={`title-${_idxRoot}`} className="relative flex flex-col">
<div className="relative border-b border-custom-border-100">
<div className="relative border-b border-custom-border-200">
<div className="sticky left-0 inline-flex whitespace-nowrap px-2 py-1 text-sm font-medium capitalize">
{_itemRoot?.title}
</div>
</div>
<div className="flex h-full w-full divide-x divide-custom-border-100">
<div className="flex h-full w-full divide-x divide-custom-border-200">
{_itemRoot.children &&
_itemRoot.children.length > 0 &&
_itemRoot.children.map((_item: any, _idx: any) => (
@@ -29,7 +29,7 @@ export const BiWeekChartView: FC<any> = () => {
>
<div
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
_item?.today ? `text-red-500 border-red-500` : `border-custom-border-100`
_item?.today ? `text-red-500 border-red-500` : `border-custom-border-200`
}`}
>
<div>{_item.title}</div>
@@ -7,18 +7,18 @@ export const DayChartView: FC<any> = () => {
return (
<>
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-100">
<div className="absolute flex h-full flex-grow divide-x divide-custom-border-200">
{renderView &&
renderView.length > 0 &&
renderView.map((_itemRoot: any, _idxRoot: any) => (
<div key={`title-${_idxRoot}`} className="relative flex flex-col">
<div className="relative border-b border-custom-border-100">
<div className="relative border-b border-custom-border-200">
<div className="sticky left-0 inline-flex whitespace-nowrap px-2 py-1 text-sm font-medium capitalize">
{_itemRoot?.title}
</div>
</div>
<div className="flex h-full w-full divide-x divide-custom-border-100">
<div className="flex h-full w-full divide-x divide-custom-border-200">
{_itemRoot.children &&
_itemRoot.children.length > 0 &&
_itemRoot.children.map((_item: any, _idx: any) => (
@@ -29,7 +29,7 @@ export const DayChartView: FC<any> = () => {
>
<div
className={`flex-shrink-0 border-b py-1 text-center text-sm capitalize font-medium ${
_item?.today ? `text-red-500 border-red-500` : `border-custom-border-100`
_item?.today ? `text-red-500 border-red-500` : `border-custom-border-200`
}`}
>
<div>{_item.title}</div>

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