-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 b22e062. * 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 <[email protected]> Co-authored-by: pablohashescobar <[email protected]>
- Loading branch information
1 parent
6e9f397
commit 16a7bd3
Showing
23 changed files
with
4,665 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const ArchiveIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M5.75 19.5C5.41667 19.5 5.125 19.375 4.875 19.125C4.625 18.875 4.5 18.5833 4.5 18.25V7.35417C4.5 7.14583 4.52083 6.96875 4.5625 6.82292C4.60417 6.67708 4.68056 6.54167 4.79167 6.41667L5.95833 4.83333C6.06944 4.70833 6.19792 4.62153 6.34375 4.57292C6.48958 4.52431 6.6624 4.5 6.86221 4.5H17.1378C17.3376 4.5 17.5069 4.52431 17.6458 4.57292C17.7847 4.62153 17.9097 4.70833 18.0208 4.83333L19.2083 6.41667C19.3194 6.54167 19.3958 6.67708 19.4375 6.82292C19.4792 6.96875 19.5 7.14583 19.5 7.35417V18.25C19.5 18.5833 19.375 18.875 19.125 19.125C18.875 19.375 18.5833 19.5 18.25 19.5H5.75ZM6.10417 6.70833H17.875L17.1165 5.75H6.85417L6.10417 6.70833ZM5.75 7.95833V18.25H18.25V7.95833H5.75ZM12 16.375L15.25 13.125L14.4167 12.2917L12.625 14.0833V9.89583H11.375V14.0833L9.58333 12.2917L8.75 13.125L12 16.375Z" | ||
fill={color ? color : "currentColor"} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const BellNotificationIcon: React.FC<Props> = ({ | ||
width = "24", | ||
height = "24", | ||
color = "rgb(var(--color-text-200))", | ||
className, | ||
}) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 22 22" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M4.35425 17.4154C4.15946 17.4154 3.99618 17.3491 3.8644 17.2166C3.73263 17.084 3.66675 16.9198 3.66675 16.7239C3.66675 16.5279 3.73263 16.365 3.8644 16.2352C3.99618 16.1053 4.15946 16.0404 4.35425 16.0404H5.59175V9.02786C5.59175 7.77509 5.96987 6.64071 6.72612 5.62474C7.48237 4.60877 8.47925 3.97092 9.71675 3.7112V3.04661C9.71675 2.69523 9.84099 2.40495 10.0895 2.17578C10.338 1.94661 10.6397 1.83203 10.9947 1.83203C11.3497 1.83203 11.6532 1.94661 11.9053 2.17578C12.1574 2.40495 12.2834 2.69523 12.2834 3.04661V3.7112C13.5209 3.97092 14.5216 4.60877 15.2855 5.62474C16.0494 6.64071 16.4313 7.77509 16.4313 9.02786V16.0404H17.6459C17.8407 16.0404 18.004 16.1066 18.1358 16.2392C18.2675 16.3717 18.3334 16.536 18.3334 16.7319C18.3334 16.9278 18.2675 17.0907 18.1358 17.2206C18.004 17.3504 17.8407 17.4154 17.6459 17.4154H4.35425ZM11.0001 20.1654C10.5112 20.1654 10.0834 19.9859 9.71675 19.6268C9.35008 19.2678 9.16675 18.8362 9.16675 18.332H12.8334C12.8334 18.8362 12.6539 19.2678 12.2949 19.6268C11.9358 19.9859 11.5042 20.1654 11.0001 20.1654ZM6.96675 16.0404H15.0563V9.02786C15.0563 7.88203 14.6706 6.91571 13.899 6.12891C13.1275 5.3421 12.1727 4.9487 11.0345 4.9487C9.89626 4.9487 8.93376 5.3421 8.14696 6.12891C7.36015 6.91571 6.96675 7.88203 6.96675 9.02786V16.0404Z" | ||
fill={color} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const ClockIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M12.6876 11.7513V8.1888C12.6876 8.00825 12.6286 7.85894 12.5105 7.74088C12.3924 7.62283 12.2431 7.5638 12.0626 7.5638C11.882 7.5638 11.7327 7.62283 11.6147 7.74088C11.4966 7.85894 11.4376 8.00825 11.4376 8.1888V12.0013C11.4376 12.0846 11.4515 12.161 11.4792 12.2305C11.507 12.2999 11.5487 12.3694 11.6042 12.4388L14.6042 15.543C14.7292 15.6819 14.8855 15.7478 15.073 15.7409C15.2605 15.7339 15.4167 15.668 15.5417 15.543C15.6667 15.418 15.7292 15.2652 15.7292 15.0846C15.7292 14.9041 15.6667 14.7513 15.5417 14.6263L12.6876 11.7513ZM12.0001 20.3346C10.8612 20.3346 9.7848 20.1159 8.77091 19.6784C7.75703 19.2409 6.87161 18.6437 6.11466 17.8867C5.35772 17.1298 4.7605 16.2444 4.323 15.2305C3.8855 14.2166 3.66675 13.1402 3.66675 12.0013C3.66675 10.8624 3.8855 9.78602 4.323 8.77213C4.7605 7.75825 5.35772 6.87283 6.11466 6.11589C6.87161 5.35894 7.75703 4.76172 8.77091 4.32422C9.7848 3.88672 10.8612 3.66797 12.0001 3.66797C13.139 3.66797 14.2154 3.88672 15.2292 4.32422C16.2431 4.76172 17.1286 5.35894 17.8855 6.11589C18.6424 6.87283 19.2397 7.75825 19.6772 8.77213C20.1147 9.78602 20.3334 10.8624 20.3334 12.0013C20.3334 13.1402 20.1147 14.2166 19.6772 15.2305C19.2397 16.2444 18.6424 17.1298 17.8855 17.8867C17.1286 18.6437 16.2431 19.2409 15.2292 19.6784C14.2154 20.1159 13.139 20.3346 12.0001 20.3346ZM12.0001 19.0846C13.9445 19.0846 15.6112 18.3902 17.0001 17.0013C18.389 15.6124 19.0834 13.9457 19.0834 12.0013C19.0834 10.0569 18.389 8.39019 17.0001 7.0013C15.6112 5.61241 13.9445 4.91797 12.0001 4.91797C10.0556 4.91797 8.38897 5.61241 7.00008 7.0013C5.61119 8.39019 4.91675 10.0569 4.91675 12.0013C4.91675 13.9457 5.61119 15.6124 7.00008 17.0013C8.38897 18.3902 10.0556 19.0846 12.0001 19.0846Z" | ||
fill={color ? color : "currentColor"} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const SingleCommentCard: React.FC<Props> = ({ | ||
width = "24", | ||
height = "24", | ||
className, | ||
color, | ||
}) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M3.66663 20.3346V4.91797C3.66663 4.58464 3.79163 4.29297 4.04163 4.04297C4.29163 3.79297 4.58329 3.66797 4.91663 3.66797H19.0833C19.4166 3.66797 19.7083 3.79297 19.9583 4.04297C20.2083 4.29297 20.3333 4.58464 20.3333 4.91797V15.7513C20.3333 16.0846 20.2083 16.3763 19.9583 16.6263C19.7083 16.8763 19.4166 17.0013 19.0833 17.0013H6.99996L3.66663 20.3346ZM6.45829 15.7513H19.0833V4.91797H4.91663V17.418L6.45829 15.7513Z" | ||
fill={color ? color : "currentColor"} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const SortIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M10.9583 17C10.7778 17 10.6285 16.941 10.5104 16.8229C10.3924 16.7049 10.3333 16.5556 10.3333 16.375C10.3333 16.1944 10.3924 16.0451 10.5104 15.9271C10.6285 15.809 10.7778 15.75 10.9583 15.75H13.0417C13.2222 15.75 13.3715 15.809 13.4896 15.9271C13.6076 16.0451 13.6667 16.1944 13.6667 16.375C13.6667 16.5556 13.6076 16.7049 13.4896 16.8229C13.3715 16.941 13.2222 17 13.0417 17H10.9583ZM5.125 8.25C4.94444 8.25 4.79514 8.19097 4.67708 8.07292C4.55903 7.95486 4.5 7.80556 4.5 7.625C4.5 7.44444 4.55903 7.29514 4.67708 7.17708C4.79514 7.05903 4.94444 7 5.125 7H18.875C19.0556 7 19.2049 7.05903 19.3229 7.17708C19.441 7.29514 19.5 7.44444 19.5 7.625C19.5 7.80556 19.441 7.95486 19.3229 8.07292C19.2049 8.19097 19.0556 8.25 18.875 8.25H5.125ZM7.625 12.625C7.44444 12.625 7.29514 12.566 7.17708 12.4479C7.05903 12.3299 7 12.1806 7 12C7 11.8194 7.05903 11.6701 7.17708 11.5521C7.29514 11.434 7.44444 11.375 7.625 11.375H16.375C16.5556 11.375 16.7049 11.434 16.8229 11.5521C16.941 11.6701 17 11.8194 17 12C17 12.1806 16.941 12.3299 16.8229 12.4479C16.7049 12.566 16.5556 12.625 16.375 12.625H7.625Z" | ||
fill={color ? color : "currentColor"} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from "react"; | ||
|
||
import type { Props } from "./types"; | ||
|
||
export const XMarkIcon: React.FC<Props> = ({ width = "24", height = "24", className, color }) => ( | ||
<svg | ||
width={width} | ||
height={height} | ||
className={className} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M12 12.875L7.625 17.25C7.5 17.375 7.35417 17.4375 7.1875 17.4375C7.02083 17.4375 6.875 17.375 6.75 17.25C6.625 17.125 6.5625 16.9792 6.5625 16.8125C6.5625 16.6458 6.625 16.5 6.75 16.375L11.125 12L6.75 7.625C6.625 7.5 6.5625 7.35417 6.5625 7.1875C6.5625 7.02083 6.625 6.875 6.75 6.75C6.875 6.625 7.02083 6.5625 7.1875 6.5625C7.35417 6.5625 7.5 6.625 7.625 6.75L12 11.125L16.375 6.75C16.5 6.625 16.6458 6.5625 16.8125 6.5625C16.9792 6.5625 17.125 6.625 17.25 6.75C17.375 6.875 17.4375 7.02083 17.4375 7.1875C17.4375 7.35417 17.375 7.5 17.25 7.625L12.875 12L17.25 16.375C17.375 16.5 17.4375 16.6458 17.4375 16.8125C17.4375 16.9792 17.375 17.125 17.25 17.25C17.125 17.375 16.9792 17.4375 16.8125 17.4375C16.6458 17.4375 16.5 17.375 16.375 17.25L12 12.875Z" | ||
fill={color ? color : "currentColor"} | ||
/> | ||
</svg> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./notification-card"; | ||
export * from "./notification-popover"; | ||
export * from "./select-snooze-till-modal"; |
200 changes: 200 additions & 0 deletions
200
apps/app/components/notifications/notification-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import React from "react"; | ||
|
||
// next | ||
import Image from "next/image"; | ||
import { useRouter } from "next/router"; | ||
|
||
// hooks | ||
import useToast from "hooks/use-toast"; | ||
|
||
// icons | ||
import { Icon } from "components/ui"; | ||
|
||
// helper | ||
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; | ||
import { formatDateDistance, renderShortDateWithYearFormat } from "helpers/date-time.helper"; | ||
|
||
// type | ||
import type { IUserNotification } from "types"; | ||
|
||
type NotificationCardProps = { | ||
notification: IUserNotification; | ||
markNotificationReadStatus: (notificationId: string) => Promise<void>; | ||
markNotificationArchivedStatus: (notificationId: string) => Promise<void>; | ||
setSelectedNotificationForSnooze: (notificationId: string) => void; | ||
markSnoozeNotification: (notificationId: string, dateTime?: Date | undefined) => Promise<void>; | ||
}; | ||
|
||
export const NotificationCard: React.FC<NotificationCardProps> = (props) => { | ||
const { | ||
notification, | ||
markNotificationReadStatus, | ||
markNotificationArchivedStatus, | ||
setSelectedNotificationForSnooze, | ||
markSnoozeNotification, | ||
} = props; | ||
|
||
const router = useRouter(); | ||
const { workspaceSlug } = router.query; | ||
|
||
const { setToastAlert } = useToast(); | ||
|
||
return ( | ||
<div | ||
key={notification.id} | ||
onClick={() => { | ||
markNotificationReadStatus(notification.id); | ||
router.push( | ||
`/${workspaceSlug}/projects/${notification.project}/issues/${notification.data.issue.id}` | ||
); | ||
}} | ||
className={`px-4 ${ | ||
notification.read_at === null ? "bg-custom-primary-70/10" : "hover:bg-custom-background-200" | ||
}`} | ||
> | ||
<div className="relative group flex items-center gap-3 py-3 cursor-pointer border-b-2 border-custom-border-200"> | ||
{notification.read_at === null && ( | ||
<span className="absolute top-1/2 -left-2 -translate-y-1/2 w-1.5 h-1.5 bg-custom-primary-100 rounded-full" /> | ||
)} | ||
<div className="flex w-full pl-2"> | ||
<div className="pl-0 p-2"> | ||
<div className="relative w-12 h-12 rounded-full"> | ||
{notification.triggered_by_details.avatar && | ||
notification.triggered_by_details.avatar !== "" ? ( | ||
<Image | ||
src={notification.triggered_by_details.avatar} | ||
alt="profile image" | ||
layout="fill" | ||
objectFit="cover" | ||
className="rounded-full" | ||
/> | ||
) : ( | ||
<div className="w-12 h-12 bg-custom-background-100 rounded-full flex justify-center items-center"> | ||
<span className="text-custom-text-100 font-semibold text-lg"> | ||
{notification.triggered_by_details.first_name[0].toUpperCase()} | ||
</span> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
<div className="w-full flex flex-col overflow-hidden"> | ||
<div> | ||
<p> | ||
<span className="font-semibold text-custom-text-200"> | ||
{notification.triggered_by_details.first_name}{" "} | ||
{notification.triggered_by_details.last_name}{" "} | ||
</span> | ||
{notification.data.issue_activity.field !== "comment" && | ||
notification.data.issue_activity.verb}{" "} | ||
{notification.data.issue_activity.field === "comment" | ||
? "commented" | ||
: notification.data.issue_activity.field === "None" | ||
? null | ||
: replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)}{" "} | ||
{notification.data.issue_activity.field !== "comment" && | ||
notification.data.issue_activity.field !== "None" | ||
? "to" | ||
: ""} | ||
<span className="font-semibold text-custom-text-200"> | ||
{" "} | ||
{notification.data.issue_activity.field !== "None" ? ( | ||
notification.data.issue_activity.field !== "comment" ? ( | ||
notification.data.issue_activity.field === "target_date" ? ( | ||
renderShortDateWithYearFormat(notification.data.issue_activity.new_value) | ||
) : notification.data.issue_activity.field === "attachment" ? ( | ||
"the issue" | ||
) : stripHTML(notification.data.issue_activity.new_value).length > 55 ? ( | ||
stripHTML(notification.data.issue_activity.new_value).slice(0, 50) + "..." | ||
) : ( | ||
stripHTML(notification.data.issue_activity.new_value) | ||
) | ||
) : ( | ||
<span> | ||
{`"`} | ||
{notification.data.issue_activity.new_value.length > 55 | ||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..." | ||
: notification.data.issue_activity.issue_comment} | ||
{`"`} | ||
</span> | ||
) | ||
) : ( | ||
"the issue and assigned it to you." | ||
)} | ||
</span> | ||
</p> | ||
</div> | ||
|
||
<div className="w-full flex items-center justify-between mt-3"> | ||
<p className="truncate inline max-w-lg text-custom-text-300 text-sm mr-3"> | ||
{notification.data.issue.identifier}-{notification.data.issue.sequence_id}{" "} | ||
{notification.data.issue.name} | ||
</p> | ||
<p className="text-custom-text-300 text-xs"> | ||
{formatDateDistance(notification.created_at)} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div className="absolute py-1 flex gap-x-3 right-0 top-3 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto"> | ||
{[ | ||
{ | ||
id: 1, | ||
name: notification.read_at ? "Mark as Unread" : "Mark as Read", | ||
icon: "chat_bubble", | ||
onClick: () => { | ||
markNotificationReadStatus(notification.id).then(() => { | ||
setToastAlert({ | ||
title: notification.read_at | ||
? "Notification marked as unread" | ||
: "Notification marked as read", | ||
type: "success", | ||
}); | ||
}); | ||
}, | ||
}, | ||
{ | ||
id: 2, | ||
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification", | ||
icon: "archive", | ||
onClick: () => { | ||
markNotificationArchivedStatus(notification.id).then(() => { | ||
setToastAlert({ | ||
title: notification.archived_at | ||
? "Notification un-archived" | ||
: "Notification archived", | ||
type: "success", | ||
}); | ||
}); | ||
}, | ||
}, | ||
{ | ||
id: 3, | ||
name: notification.snoozed_till ? "Unsnooze Notification" : "Snooze Notification", | ||
icon: "schedule", | ||
onClick: () => { | ||
if (notification.snoozed_till) | ||
markSnoozeNotification(notification.id).then(() => { | ||
setToastAlert({ title: "Notification un-snoozed", type: "success" }); | ||
}); | ||
else setSelectedNotificationForSnooze(notification.id); | ||
}, | ||
}, | ||
].map((item) => ( | ||
<button | ||
type="button" | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
item.onClick(); | ||
}} | ||
key={item.id} | ||
className="text-sm flex w-full items-center gap-x-2 hover:bg-custom-background-100 p-0.5 rounded" | ||
> | ||
<Icon iconName={item.icon} className="h-5 w-5 text-custom-text-300" /> | ||
</button> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.