diff --git a/apiserver/plane/bgtasks/email_notification_task.py b/apiserver/plane/bgtasks/email_notification_task.py index cf72555851..cc9588ca6e 100644 --- a/apiserver/plane/bgtasks/email_notification_task.py +++ b/apiserver/plane/bgtasks/email_notification_task.py @@ -148,10 +148,12 @@ def send_email_notification( template_data = [] total_changes = 0 comments = [] + actors_involved = [] for actor_id, changes in data.items(): actor = User.objects.get(pk=actor_id) total_changes = total_changes + len(changes) comment = changes.pop("comment", False) + actors_involved.append(actor_id) if comment: comments.append( { @@ -191,6 +193,7 @@ def send_email_notification( context = { "data": template_data, "summary": summary, + "actors_involved": len(set(actors_involved)), "issue": { "issue_identifier": f"{str(issue.project.identifier)}-{str(issue.sequence_id)}", "name": issue.name, @@ -200,6 +203,9 @@ def send_email_notification( "email": receiver.email, }, "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", + "project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/", + "workspace":str(issue.project.workspace.slug), + "project": str(issue.project.name), "user_preference": f"{base_api}/profile/preferences/email", "comments": comments, } diff --git a/apiserver/templates/emails/notifications/issue-updates.html b/apiserver/templates/emails/notifications/issue-updates.html index 4374846df7..bdc6a53a3f 100644 --- a/apiserver/templates/emails/notifications/issue-updates.html +++ b/apiserver/templates/emails/notifications/issue-updates.html @@ -1,978 +1,1108 @@ - - - - Updates on issue - - - -
- -
- - - - -
-
- -
-
-
- -
+ + + + Updates on issue + + + + + -
- - - - -
-

- {{ issue.issue_identifier }} updates -

-

- {{ issue.name }}: {{ issue.issue_identifier }} -

-
-
- -

- {% if data.1 %}{{ data|length }}{% endif %} {{ summary }} - - {{ data.0.actor_detail.first_name}} - {{data.0.actor_detail.last_name }} - -

- {% if comments.0 %} -

- {{ comments|length }} {% if comments|length == 1 %}comment was{% else %}comments were{% endif %} left by - - {% if comments|length == 1 %} - {{ data.0.actor_detail.first_name }} - {{ data.0.actor_detail.last_name }} - {% else %} - {{ data.0.actor_detail.first_name }} - {{ data.0.actor_detail.last_name }} and others - {% endif %} - -

- {% endif %} - {% if mentions and comments.0 and data.0 %} -

- There are 3 new updates, added 1 new comment and, you were - - @{{ data.0.actor_detail.first_name}} - {{data.0.actor_detail.last_name }} - - mentioned a comment of this issue. -

- {% endif %} - {% for update in data %} {% if update.changes.name %} - -

- The issue title has been updated from “{{update.changes.user.old_value.0}}“ to "{{update.changes.user.new_value|last}}" -

- {% endif %} - - {% if data %} -
- -
-

- Updates -

-
- -
- - - - + {/* first column/ issue name and key column */} -
- {% if update.actor_detail.avatar_url %} - - {% else %} - - - - - - -
+ +
+ + + - -
+
- - {{ update.actor_detail.first_name.0 }} - -
- {% endif %} + +
-

- {{ update.actor_detail.first_name }} {{ update.actor_detail.last_name }} -

-
-

- {{ update.activity_time }} -

-
- {% if update.changes.target_date %} - - - - - - - -
- - -
-

- Due Date: -

-
-
-

- {{ update.changes.target_date.new_value.0 }} -

-
- {% endif %} {% if update.changes.duplicate %} - - - - - - - - -
- - - Duplicate: - - - {% for duplicate in update.changes.duplicate.new_value %} - - {{ duplicate }} - - {% endfor %} -
- {% endif %} - - {% if update.changes.assignees %} - - - - - {% if update.changes.assignees.new_value.0 %} - - {% endif %} {% if update.changes.assignees.new_value.1 %} - - {% endif %} {% if update.changes.assignees.old_value.0 %} - - {% endif %} {% if update.changes.assignees.old_value.1 %} - - {% endif %} - -
- - -

- Assignees: -

-
-

- {{ update.changes.assignees.new_value.0 }} -

-
-

- +{{ update.changes.assignees.new_value|length|add:"-1"}} - more -

-
-

- {{update.changes.assignees.old_value.0}} -

-
-

- +{{ update.changes.assignees.old_value|length|add:"-1"}} - more -

-
- {% endif %} {% if update.changes.labels %} - - - - - - {% if update.changes.labels.new_value.0 %} - - {% endif %} - {% if update.changes.labels.new_value.1 %} - - {% endif %} - {% if update.changes.labels.old_value.0 %} - - {% endif %} - {% if update.changes.labels.old_value.1 %} - - {% endif %} - -
- - -

- Labels: -

-
-

- {{update.changes.labels.new_value.0}} -

-
-

- +{{ update.changes.labels.new_value|length|add:"-1"}} more -

-
-

- {{update.changes.labels.old_value.0}} -

-
-

- +{{ update.changes.labels.old_value|length|add:"-1"}} more -

-
- {% endif %} - - {% if update.changes.state %} - - - - - - - - - - -
- - -

- State: -

-
- - -

- {{ update.changes.state.old_value.0 }} -

-
- - - - -

- {{update.changes.state.new_value|last }} -

-
- {% endif %} {% if update.changes.link %} - - - - - - - -
- - -

- Links: -

-
- {% for link in update.changes.link.new_value %} - - {{ link }} - - {% endfor %} - {% if update.changes.link.old_value|length > 0 %} - {% if update.changes.link.old_value.0 != "None" %} -

- 2 Links were removed -

- {% endif %} - {% endif %} -
- {% endif %} - {% if update.changes.priority %} - - - - - - - - - - -
- - -

- Priority: -

-
-

- {{ update.changes.priority.old_value.0 }} -

-
- - -

- {{ update.changes.priority.new_value|last }} -

-
- {% endif %} - {% if update.changes.blocking.new_value %} - - - - - - - - -
- - - Blocking: - - - {% for blocking in update.changes.blocking.new_value %} - - {{blocking}} - - {% endfor %} -
- {% endif %} - - - {% endif %} - - {% endfor %} {% if comments.0 %} - -
- -

- Comments -

- - {% for comment in comments %} - - - - - +
- {% if comment.actor_detail.avatar_url %} - - {% else %} - - - - -
- - {{ comment.actor_detail.first_name.0 }} - -
- {% endif %} -
- - - - - {% for actor_comment in comment.actor_comments.new_value %} - - - - {% endfor %} -
-

- {{ comment.actor_detail.first_name }} {{ comment.actor_detail.last_name }} -

-
-
-

- {{ actor_comment|safe }} -

-
-
-
- {% endfor %} -
- {% endif %} - - - - - - - - -
-
- This email was sent to - {{ receiver.email }}. - If you'd rather not receive this kind of email, - you can unsubscribe to the issue - or - manage your email preferences. - -
- - + + {% if actors_involved > 0 %} + {% if data|length > 0 and comments|length == 0 %} +

+ + {{ data.0.actor_detail.first_name}} + {{data.0.actor_detail.last_name }} + + made {{data|length}} {% if data|length > 1 %}updates{% else %}update{% endif %} to the issue. +

+ {% elif data|length == 0 and comments|length > 0 %} +

+ + {{ comments.0.actor_detail.first_name}} + {{comments.0.actor_detail.last_name }} + + added {{comments|length}} new {% if comments|length > 1 %}comments{% else %}comment{% endif %}. +

+ {% elif data|length > 0 and comments|length > 0 %} +

+ + {{ data.0.actor_detail.first_name}} + {{data.0.actor_detail.last_name }} + + made {{data|length}} {% if data|length > 1 %}updates{% else %}update{% endif %} and added {{comments|length}} new {% if comments|length > 1 %}comments{% else %}comment{% endif %} on the issue. +

+ {% endif %} + {% else %} +

+ There are {{ data|length }} new updates and {{comments|length}} new comments on the issue +

+ {% endif %} + {% for update in data %} {% if update.changes.name %} + +

+ The issue title has been updated to {{ issue.name}} +

+ {% endif %} + + {% if data %} +
+ +
+

+ Updates +

+
+ +
+ + + + + + + +
+ {% if update.actor_detail.avatar_url %} + + {% else %} + + + + +
+ + {{ update.actor_detail.first_name.0 }} + +
+ {% endif %} +
+

+ {{ update.actor_detail.first_name }} {{ update.actor_detail.last_name }} +

+
+

+ {{ update.activity_time }} +

+
+ {% if update.changes.target_date %} + + + + + + + +
+ + +
+

+ Due Date: +

+
+
+ {% if update.changes.target_date.new_value.0 %} +

+ {{ update.changes.target_date.new_value.0 }} +

+ {% else %} +

+ {{ update.changes.target_date.old_value.0 }} +

+ {% endif %} +
+ {% endif %} {% if update.changes.duplicate %} + + + + + {% if update.changes.duplicate.new_value.0 %} + + {% endif %} + {% if update.changes.duplicate.new_value.2 %} + + {% endif %} + {% if update.changes.duplicate.old_value.0 %} + + {% endif %} + {% if update.changes.duplicate.old_value.2 %} + + {% endif %} + +
+ + + Duplicate: + + + {% for duplicate in update.changes.duplicate.new_value|slice:":2" %} + + {{ duplicate }} + + {% endfor %} + + + +{{ update.changes.duplicate.new_value|length|add:"-2" }} + more + + + {% for duplicate in update.changes.duplicate.old_value|slice:":2" %} + + {{ duplicate }} + + {% endfor %} + + + +{{ update.changes.duplicate.old_value|length|add:"-2" }} + more + +
+ {% endif %} + + {% if update.changes.assignees %} + + + + + +
+ + + Assignee: + + + {% if update.changes.assignees.new_value.0 %} + + {{update.changes.assignees.new_value.0}} + + {% endif %} + {% if update.changes.assignees.new_value.1 %} + + +{{ update.changes.assignees.new_value|length|add:"-1"}} more + + {% endif %} + {% if update.changes.assignees.old_value.0 %} + + {{update.changes.assignees.old_value.0}} + + {% endif %} + {% if update.changes.assignees.old_value.1 %} + + +{{ update.changes.assignees.old_value|length|add:"-1"}} more + + {% endif %} +
+ {% endif %} {% if update.changes.labels %} + + + + + + +
+ + + Labels: + + + {% if update.changes.labels.new_value.0 %} + + {{update.changes.labels.new_value.0}} + + {% endif %} + {% if update.changes.labels.new_value.1 %} + + +{{ update.changes.labels.new_value|length|add:"-1"}} more + + {% endif %} + {% if update.changes.labels.old_value.0 %} + + {{update.changes.labels.old_value.0}} + + {% endif %} + {% if update.changes.labels.old_value.1 %} + + +{{ update.changes.labels.old_value|length|add:"-1"}} more + + {% endif %} +
+ {% endif %} + + {% if update.changes.state %} + + + + + + + + + + +
+ + +

+ State: +

+
+ + +

+ {{ update.changes.state.old_value.0 }} +

+
+ + + + +

+ {{update.changes.state.new_value|last }} +

+
+ {% endif %} {% if update.changes.link %} + + + + + + + +
+ + +

+ Links: +

+
+ {% for link in update.changes.link.new_value %} + + {{ link }} + + {% endfor %} + {% if update.changes.link.old_value|length > 0 %} + {% if update.changes.link.old_value.0 != "None" %} +

+ 2 Links were removed +

+ {% endif %} + {% endif %} +
+ {% endif %} + {% if update.changes.priority %} + + + + + + + + + +
+ + +

+ Priority: +

+
+

+ {{ update.changes.priority.old_value.0 }} +

+
+ + +

+ {{ update.changes.priority.new_value|last }} +

+
+ {% endif %} + {% if update.changes.blocking.new_value %} + + + + + {% if update.changes.blocking.new_value.0 %} + + {% endif %} + {% if update.changes.blocking.new_value.2 %} + + {% endif %} + {% if update.changes.blocking.old_value.0 %} + + {% endif %} + {% if update.changes.blocking.old_value.2 %} + + {% endif %} + +
+ + + Blocking: + + + {% for blocking in update.changes.blocking.new_value|slice:":2" %} + + {{ blocking }} + + {% endfor %} + + + +{{ update.changes.blocking.new_value|length|add:"-2" }} + more + + + {% for blocking in update.changes.blocking.old_value|slice:":2" %} + + {{ blocking }} + + {% endfor %} + + + +{{ update.changes.blocking.old_value|length|add:"-2" }} + more + +
+ {% endif %} +
+
+ {% endif %} + + {% endfor %} {% if comments.0 %} + +
+ +

+ Comments +

+ + {% for comment in comments %} + + + + + +
+ {% if comment.actor_detail.avatar_url %} + + {% else %} + + + + +
+ + {{ comment.actor_detail.first_name.0 }} + +
+ {% endif %} +
+ + + + + {% for actor_comment in comment.actor_comments.new_value %} + + + + {% endfor %} +
+

+ {{ comment.actor_detail.first_name }} {{ comment.actor_detail.last_name }} +

+
+
+

+ {{ actor_comment|safe }} +

+
+
+
+ {% endfor %} +
+ {% endif %}
-
- - - + +
+ View issue +
+
+ + + + + + +
+
+ This email was sent to + {{ receiver.email }}. + If you'd rather not receive this kind of email, + you can unsubscribe to the issue + or + manage your email preferences. + + +
+
+ + + \ No newline at end of file diff --git a/web/components/account/sign-in-forms/optional-set-password.tsx b/web/components/account/sign-in-forms/optional-set-password.tsx index 1669811cb8..d7a5952984 100644 --- a/web/components/account/sign-in-forms/optional-set-password.tsx +++ b/web/components/account/sign-in-forms/optional-set-password.tsx @@ -8,6 +8,8 @@ import useToast from "hooks/use-toast"; import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; +// icons +import { Eye, EyeOff } from "lucide-react"; type Props = { email: string; @@ -31,6 +33,7 @@ export const SignInOptionalSetPasswordForm: React.FC = (props) => { const { email, handleSignInRedirection } = props; // states const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -114,17 +117,30 @@ export const SignInOptionalSetPasswordForm: React.FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange, ref } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index fd4ccbf403..fe20d5b107 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // services import { AuthService } from "services/auth.service"; // hooks @@ -40,6 +40,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { const { email, handleStepChange, handleEmailClear, onSubmit } = props; // states const [isSendingUniqueCode, setIsSendingUniqueCode] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); const { @@ -157,15 +158,28 @@ export const SignInPasswordForm: React.FC = observer((props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />
diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx index 38fdaeca1d..db14f0ccb5 100644 --- a/web/components/account/sign-up-forms/optional-set-password.tsx +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -10,6 +10,8 @@ import { Button, Input } from "@plane/ui"; import { checkEmailValidity } from "helpers/string.helper"; // constants import { ESignUpSteps } from "components/account"; +// icons +import { Eye, EyeOff } from "lucide-react"; type Props = { email: string; @@ -34,6 +36,7 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { const { email, handleSignInRedirection } = props; // states const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -119,16 +122,29 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index 6ff6753df2..293e03ef87 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -1,8 +1,8 @@ -import React from "react"; +import React, { useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // services import { AuthService } from "services/auth.service"; // hooks @@ -32,6 +32,8 @@ const authService = new AuthService(); export const SignUpPasswordForm: React.FC = observer((props) => { const { onSubmit } = props; + // states + const [showPassword, setShowPassword] = useState(false); // toast alert const { setToastAlert } = useToast(); // form info @@ -112,15 +114,28 @@ export const SignUpPasswordForm: React.FC = observer((props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +

+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/gantt-chart/blocks/blocks-display.tsx b/web/components/gantt-chart/blocks/blocks-display.tsx index 02d2eb8657..e13be116b6 100644 --- a/web/components/gantt-chart/blocks/blocks-display.tsx +++ b/web/components/gantt-chart/blocks/blocks-display.tsx @@ -1,9 +1,11 @@ import { FC } from "react"; // hooks +import { useIssueDetail } from "hooks/store"; import { useChart } from "../hooks"; // helpers import { ChartAddBlock, ChartDraggable } from "components/gantt-chart"; import { renderFormattedPayloadDate } from "helpers/date-time.helper"; +import { cn } from "helpers/common.helper"; // types import { IBlockUpdateData, IGanttBlock } from "../types"; @@ -31,6 +33,7 @@ export const GanttChartBlocks: FC = (props) => { } = props; const { activeBlock, dispatch } = useChart(); + const { peekIssue } = useIssueDetail(); // update the active block on hover const updateActiveBlock = (block: IGanttBlock | null) => { @@ -88,7 +91,14 @@ export const GanttChartBlocks: FC = (props) => { return (

updateActiveBlock(block)} onMouseLeave={() => updateActiveBlock(null)} > diff --git a/web/components/gantt-chart/sidebar/sidebar.tsx b/web/components/gantt-chart/sidebar/sidebar.tsx index 062b764514..bca39a0bd6 100644 --- a/web/components/gantt-chart/sidebar/sidebar.tsx +++ b/web/components/gantt-chart/sidebar/sidebar.tsx @@ -3,12 +3,14 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea import { MoreVertical } from "lucide-react"; // hooks import { useChart } from "components/gantt-chart/hooks"; +import { useIssueDetail } from "hooks/store"; // ui import { Loader } from "@plane/ui"; // components import { GanttQuickAddIssueForm, IssueGanttSidebarBlock } from "components/issues"; // helpers import { findTotalDaysInRange } from "helpers/date-time.helper"; +import { cn } from "helpers/common.helper"; // types import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types"; import { TIssue } from "@plane/types"; @@ -45,6 +47,7 @@ export const IssueGanttSidebar: React.FC = (props) => { const { cycleId } = router.query; const { activeBlock, dispatch } = useChart(); + const { peekIssue } = useIssueDetail(); // update the active block on hover const updateActiveBlock = (block: IGanttBlock | null) => { @@ -104,7 +107,7 @@ export const IssueGanttSidebar: React.FC = (props) => { {(droppableProvided) => (
@@ -130,7 +133,14 @@ export const IssueGanttSidebar: React.FC = (props) => { > {(provided, snapshot) => (
updateActiveBlock(block)} onMouseLeave={() => updateActiveBlock(null)} ref={provided.innerRef} diff --git a/web/components/instance/setup-form/sign-in-form.tsx b/web/components/instance/setup-form/sign-in-form.tsx index f34d807c91..c4a9de6a39 100644 --- a/web/components/instance/setup-form/sign-in-form.tsx +++ b/web/components/instance/setup-form/sign-in-form.tsx @@ -1,6 +1,6 @@ -import { FC } from "react"; +import { FC, useState } from "react"; import { useForm, Controller } from "react-hook-form"; -import { XCircle } from "lucide-react"; +import { Eye, EyeOff, XCircle } from "lucide-react"; // hooks import { useUser } from "hooks/store"; // ui @@ -24,6 +24,8 @@ export interface IInstanceSetupEmailForm { export const InstanceSetupSignInForm: FC = (props) => { const { handleNextStep } = props; + // states + const [showPassword, setShowPassword] = useState(false); // store hooks const { fetchCurrentUser } = useUser(); // form info @@ -107,14 +109,27 @@ export const InstanceSetupSignInForm: FC = (props) => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />

diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx index 2e612ad34c..4a1f79bee5 100644 --- a/web/components/issues/issue-detail/inbox/main-content.tsx +++ b/web/components/issues/issue-detail/inbox/main-content.tsx @@ -79,7 +79,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {

- +
); diff --git a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx index 28bd00b1f0..172a0bb823 100644 --- a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx +++ b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx @@ -17,7 +17,6 @@ const fileService = new FileService(); type TIssueCommentCreate = { workspaceSlug: string; activityOperations: TActivityOperations; - disabled: boolean; showAccessSpecifier?: boolean; }; @@ -40,7 +39,7 @@ const commentAccess: commentAccessType[] = [ ]; export const IssueCommentCreate: FC = (props) => { - const { workspaceSlug, activityOperations, disabled, showAccessSpecifier = false } = props; + const { workspaceSlug, activityOperations, showAccessSpecifier = false } = props; const workspaceStore = useWorkspace(); const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; @@ -94,7 +93,7 @@ export const IssueCommentCreate: FC = (props) => { } submitButton={
) : activityTab === "activity" ? ( @@ -166,14 +162,11 @@ export const IssueActivity: FC = observer((props) => { activityOperations={activityOperations} showAccessSpecifier={project.is_deployed} /> - {!disabled && ( - - )} +
)}
diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx index 31400fde2e..0755258017 100644 --- a/web/components/issues/issue-detail/main-content.tsx +++ b/web/components/issues/issue-detail/main-content.tsx @@ -99,7 +99,7 @@ export const IssueMainContent: React.FC = observer((props) => { disabled={!is_editable} /> - + ); }); diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx index 5711c89f64..f66bf2ec09 100644 --- a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -6,7 +6,8 @@ import { MoreHorizontal } from "lucide-react"; import { Tooltip, ControlLink } from "@plane/ui"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; -// ui +// helpers +import { cn } from "helpers/common.helper"; // types import { TIssue, TIssueMap } from "@plane/types"; import { useApplication, useIssueDetail, useProject, useProjectState } from "hooks/store"; @@ -26,7 +27,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { } = useApplication(); const { getProjectById } = useProject(); const { getProjectStates } = useProjectState(); - const { setPeekIssue } = useIssueDetail(); + const { peekIssue, setPeekIssue } = useIssueDetail(); // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -84,11 +85,18 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { )}
= observer((prop export const KanbanIssueBlock: React.FC = memo((props) => { const { + peekIssueId, issueId, issuesMap, displayProperties, @@ -121,9 +126,12 @@ export const KanbanIssueBlock: React.FC = memo((props) => {
)}
= (props) => { sub_group_id, columnId, issuesMap, + peekIssueId, issueIds, displayProperties, isDragDisabled, @@ -44,6 +46,7 @@ const KanbanIssueBlocksListMemo: React.FC = (props) => { return ( = observer((props) => { const project = useProject(); const label = useLabel(); const projectState = useProjectState(); + const { peekIssue } = useIssueDetail(); const list = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member); @@ -120,6 +121,7 @@ const GroupByKanBan: React.FC = observer((props) => { groupId={_list.id} issuesMap={issuesMap} issueIds={issueIds} + peekIssueId={peekIssue?.issueId ?? ""} displayProperties={displayProperties} sub_group_by={sub_group_by} group_by={group_by} diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx index 76d456b5ee..1a25c563e7 100644 --- a/web/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -17,6 +17,7 @@ import { EIssueActions } from "../types"; interface IKanbanGroup { groupId: string; issuesMap: IIssueMap; + peekIssueId?: string; issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues; displayProperties: IIssueDisplayProperties | undefined; sub_group_by: string | null; @@ -47,6 +48,7 @@ export const KanbanGroup = (props: IKanbanGroup) => { issuesMap, displayProperties, issueIds, + peekIssueId, isDragDisabled, handleIssues, quickActions, @@ -118,6 +120,7 @@ export const KanbanGroup = (props: IKanbanGroup) => { sub_group_id={sub_group_id} columnId={groupId} issuesMap={issuesMap} + peekIssueId={peekIssueId} issueIds={(issueIds as TGroupedIssues)?.[groupId] || []} displayProperties={displayProperties} isDragDisabled={isDragDisabled} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index da63a834e5..b2222a69ea 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -5,6 +5,8 @@ import { IssueProperties } from "../properties/all-properties"; import { useApplication, useIssueDetail, useProject } from "hooks/store"; // ui import { Spinner, Tooltip, ControlLink } from "@plane/ui"; +// helper +import { cn } from "helpers/common.helper"; // types import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; import { EIssueActions } from "../types"; @@ -25,7 +27,7 @@ export const IssueBlock: React.FC = observer((props: IssueBlock router: { workspaceSlug, projectId }, } = useApplication(); const { getProjectById } = useProject(); - const { setPeekIssue } = useIssueDetail(); + const { peekIssue, setPeekIssue } = useIssueDetail(); const updateIssue = (issueToUpdate: TIssue) => { handleIssues(issueToUpdate, EIssueActions.UPDATE); @@ -47,7 +49,15 @@ export const IssueBlock: React.FC = observer((props: IssueBlock return ( <> -
+
{displayProperties && displayProperties?.key && (
{projectDetails?.identifier}-{issue.sequence_id} diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index 729cd6c68b..5e02d638f7 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -18,7 +18,7 @@ export const IssueBlocksList: FC = (props) => { const { issueIds, issuesMap, handleIssues, quickActions, displayProperties, canEditProperties } = props; return ( -
+
{issueIds && issueIds.length > 0 ? ( issueIds.map((issueId: string) => { if (!issueId) return null; diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx index 602c1a842e..579b8863cc 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -12,6 +12,8 @@ import { ControlLink, Tooltip } from "@plane/ui"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; import { useIssueDetail, useProject } from "hooks/store"; +// helper +import { cn } from "helpers/common.helper"; // types import { IIssueDisplayProperties, TIssue } from "@plane/types"; import { EIssueActions } from "../types"; @@ -48,7 +50,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => { const { workspaceSlug } = router.query; //hooks const { getProjectById } = useProject(); - const { setPeekIssue } = useIssueDetail(); + const { peekIssue, setPeekIssue } = useIssueDetail(); // states const [isMenuActive, setIsMenuActive] = useState(false); const [isExpanded, setExpanded] = useState(false); @@ -95,9 +97,20 @@ export const SpreadsheetIssueRow = observer((props: Props) => { return ( <> -
+
{ } = props; return ( - +
= observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} - disabled={disabled} /> ) : ( @@ -255,7 +254,6 @@ export const IssueView: FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} - disabled={disabled} /> diff --git a/web/pages/accounts/reset-password.tsx b/web/pages/accounts/reset-password.tsx index 2b893d665a..9854ec5bb2 100644 --- a/web/pages/accounts/reset-password.tsx +++ b/web/pages/accounts/reset-password.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import { ReactElement, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import { Controller, useForm } from "react-hook-form"; @@ -19,6 +19,8 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; import { checkEmailValidity } from "helpers/string.helper"; // type import { NextPageWithLayout } from "lib/types"; +// icons +import { Eye, EyeOff } from "lucide-react"; type TResetPasswordFormValues = { email: string; @@ -37,6 +39,8 @@ const ResetPasswordPage: NextPageWithLayout = () => { // router const router = useRouter(); const { uidb64, token, email } = router.query; + // states + const [showPassword, setShowPassword] = useState(false); // toast const { setToastAlert } = useToast(); // sign in redirection hook @@ -117,15 +121,28 @@ const ResetPasswordPage: NextPageWithLayout = () => { required: "Password is required", }} render={({ field: { value, onChange } }) => ( - +
+ + {showPassword ? ( + setShowPassword(false)} + /> + ) : ( + setShowPassword(true)} + /> + )} +
)} />