mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: improved UX with toast alerts and other interactions
refactor: changed classnames according to new theme structure, changed all icons to material icons
This commit is contained in:
parent
be39b097ab
commit
7045f80ec6
@ -34,7 +34,7 @@ import {
|
|||||||
SidebarEstimateSelect,
|
SidebarEstimateSelect,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
// ui
|
// ui
|
||||||
import { Input, Spinner, CustomDatePicker } from "components/ui";
|
import { Input, Spinner, CustomDatePicker, Icon } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import {
|
||||||
TagIcon,
|
TagIcon,
|
||||||
@ -293,16 +293,19 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<button
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
||||||
type="button"
|
<button
|
||||||
className="rounded-md border border-custom-primary-100 px-4 py-1 text-xs text-custom-primary-100 shadow-sm duration-300 focus:outline-none"
|
type="button"
|
||||||
onClick={() => {
|
className="rounded-md flex items-center gap-2 border border-custom-primary-100 px-2 py-1 text-xs text-custom-primary-100 shadow-sm duration-300 focus:outline-none"
|
||||||
if (subscribed) handleUnsubscribe();
|
onClick={() => {
|
||||||
else handleSubscribe();
|
if (subscribed) handleUnsubscribe();
|
||||||
}}
|
else handleSubscribe();
|
||||||
>
|
}}
|
||||||
{loading ? "Loading..." : subscribed ? "Unsubscribe" : "Subscribe"}
|
>
|
||||||
</button>
|
<Icon iconName="notifications" />
|
||||||
|
{loading ? "Loading..." : subscribed ? "Unsubscribe" : "Subscribe"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -5,9 +5,10 @@ import Image from "next/image";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUserNotification from "hooks/use-user-notifications";
|
import useToast from "hooks/use-toast";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { ArchiveIcon, ClockIcon, SingleCommentCard } from "components/icons";
|
import { Icon } from "components/ui";
|
||||||
|
|
||||||
// helper
|
// helper
|
||||||
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||||
@ -18,9 +19,10 @@ import type { IUserNotification } from "types";
|
|||||||
|
|
||||||
type NotificationCardProps = {
|
type NotificationCardProps = {
|
||||||
notification: IUserNotification;
|
notification: IUserNotification;
|
||||||
markNotificationReadStatus: (notificationId: string) => void;
|
markNotificationReadStatus: (notificationId: string) => Promise<void>;
|
||||||
markNotificationArchivedStatus: (notificationId: string) => void;
|
markNotificationArchivedStatus: (notificationId: string) => Promise<void>;
|
||||||
setSelectedNotificationForSnooze: (notificationId: string) => void;
|
setSelectedNotificationForSnooze: (notificationId: string) => void;
|
||||||
|
markSnoozeNotification: (notificationId: string, dateTime?: Date | undefined) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||||
@ -29,11 +31,16 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
markNotificationReadStatus,
|
markNotificationReadStatus,
|
||||||
markNotificationArchivedStatus,
|
markNotificationArchivedStatus,
|
||||||
setSelectedNotificationForSnooze,
|
setSelectedNotificationForSnooze,
|
||||||
|
markSnoozeNotification,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
if (notification.data.issue_activity.field === "None") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
@ -79,7 +86,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
{notification.triggered_by_details.first_name}{" "}
|
{notification.triggered_by_details.first_name}{" "}
|
||||||
{notification.triggered_by_details.last_name}{" "}
|
{notification.triggered_by_details.last_name}{" "}
|
||||||
</span>
|
</span>
|
||||||
{notification.data.issue_activity.verb}{" "}
|
{notification.data.issue_activity.field !== "comment" &&
|
||||||
|
notification.data.issue_activity.verb}{" "}
|
||||||
{notification.data.issue_activity.field !== "comment"
|
{notification.data.issue_activity.field !== "comment"
|
||||||
? replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)
|
? replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)
|
||||||
: "commented"}{" "}
|
: "commented"}{" "}
|
||||||
@ -89,13 +97,17 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
{notification.data.issue_activity.field !== "comment" ? (
|
{notification.data.issue_activity.field !== "comment" ? (
|
||||||
notification.data.issue_activity.field === "target_date" ? (
|
notification.data.issue_activity.field === "target_date" ? (
|
||||||
renderShortDateWithYearFormat(notification.data.issue_activity.new_value)
|
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)
|
stripHTML(notification.data.issue_activity.new_value)
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
{`"`}
|
{`"`}
|
||||||
{notification.data.issue_activity.new_value.length > 50
|
{notification.data.issue_activity.new_value.length > 55
|
||||||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
||||||
: notification.data.issue_activity.issue_comment}
|
: notification.data.issue_activity.issue_comment}
|
||||||
{`"`}
|
{`"`}
|
||||||
@ -122,25 +134,43 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: notification.read_at ? "Mark as Unread" : "Mark as Read",
|
name: notification.read_at ? "Mark as Unread" : "Mark as Read",
|
||||||
icon: SingleCommentCard,
|
icon: "chat_bubble",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
markNotificationReadStatus(notification.id);
|
markNotificationReadStatus(notification.id).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.read_at
|
||||||
|
? "Notification marked as unread"
|
||||||
|
: "Notification marked as read",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification",
|
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification",
|
||||||
icon: ArchiveIcon,
|
icon: "archive",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
markNotificationArchivedStatus(notification.id);
|
markNotificationArchivedStatus(notification.id).then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.archived_at
|
||||||
|
? "Notification un-archived"
|
||||||
|
: "Notification archived",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: notification.snoozed_till ? "Unsnooze Notification" : "Snooze Notification",
|
name: notification.snoozed_till ? "Unsnooze Notification" : "Snooze Notification",
|
||||||
icon: ClockIcon,
|
icon: "schedule",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setSelectedNotificationForSnooze(notification.id);
|
if (notification.snoozed_till)
|
||||||
|
markSnoozeNotification(notification.id).then(() => {
|
||||||
|
setToastAlert({ title: "Notification un-snoozed", type: "success" });
|
||||||
|
});
|
||||||
|
else setSelectedNotificationForSnooze(notification.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
@ -153,7 +183,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
className="text-sm flex w-full items-center gap-x-2 hover:bg-custom-background-100 p-0.5 rounded"
|
className="text-sm flex w-full items-center gap-x-2 hover:bg-custom-background-100 p-0.5 rounded"
|
||||||
>
|
>
|
||||||
<item.icon className="h-5 w-5 text-custom-text-300" />
|
<Icon iconName={item.icon} className="h-5 w-5 text-custom-text-300" />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,23 +4,14 @@ import Image from "next/image";
|
|||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useTheme from "hooks/use-theme";
|
import useTheme from "hooks/use-theme";
|
||||||
// icons
|
|
||||||
import {
|
|
||||||
XMarkIcon,
|
|
||||||
ArchiveIcon,
|
|
||||||
ClockIcon,
|
|
||||||
SortIcon,
|
|
||||||
BellNotificationIcon,
|
|
||||||
} from "components/icons";
|
|
||||||
|
|
||||||
import { Popover, Transition, Menu } from "@headlessui/react";
|
import { Popover, Transition, Menu } from "@headlessui/react";
|
||||||
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
|
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUserNotification from "hooks/use-user-notifications";
|
import useUserNotification from "hooks/use-user-notifications";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { Spinner } from "components/ui";
|
import { Spinner, Icon } from "components/ui";
|
||||||
import { SnoozeNotificationModal, NotificationCard } from "components/notifications";
|
import { SnoozeNotificationModal, NotificationCard } from "components/notifications";
|
||||||
|
|
||||||
// type
|
// type
|
||||||
@ -60,6 +51,7 @@ export const NotificationPopover = () => {
|
|||||||
notificationsMutate,
|
notificationsMutate,
|
||||||
markNotificationArchivedStatus,
|
markNotificationArchivedStatus,
|
||||||
markNotificationReadStatus,
|
markNotificationReadStatus,
|
||||||
|
markSnoozeNotification,
|
||||||
} = useUserNotification();
|
} = useUserNotification();
|
||||||
|
|
||||||
// theme context
|
// theme context
|
||||||
@ -70,7 +62,12 @@ export const NotificationPopover = () => {
|
|||||||
<SnoozeNotificationModal
|
<SnoozeNotificationModal
|
||||||
isOpen={selectedNotificationForSnooze !== null}
|
isOpen={selectedNotificationForSnooze !== null}
|
||||||
onClose={() => setSelectedNotificationForSnooze(null)}
|
onClose={() => setSelectedNotificationForSnooze(null)}
|
||||||
notificationId={selectedNotificationForSnooze}
|
onSubmit={markSnoozeNotification}
|
||||||
|
notification={
|
||||||
|
notifications?.find(
|
||||||
|
(notification) => notification.id === selectedNotificationForSnooze
|
||||||
|
) || null
|
||||||
|
}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
notificationsMutate();
|
notificationsMutate();
|
||||||
setSelectedNotificationForSnooze(null);
|
setSelectedNotificationForSnooze(null);
|
||||||
@ -80,26 +77,13 @@ export const NotificationPopover = () => {
|
|||||||
{({ open: isActive, close: closePopover }) => (
|
{({ open: isActive, close: closePopover }) => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`${
|
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||||
isActive
|
isActive
|
||||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90"
|
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||||
} group flex w-full items-center gap-3 rounded-md p-2 text-sm font-medium outline-none ${
|
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||||
sidebarCollapse ? "justify-center" : ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<span className="grid h-5 w-5 flex-shrink-0 place-items-center">
|
<Icon iconName="notifications" />
|
||||||
<BellNotificationIcon
|
|
||||||
color={
|
|
||||||
isActive
|
|
||||||
? "rgb(var(--color-sidebar-text-100))"
|
|
||||||
: "rgb(var(--color-sidebar-text-200))"
|
|
||||||
}
|
|
||||||
aria-hidden="true"
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{sidebarCollapse ? null : <span>Notifications</span>}
|
{sidebarCollapse ? null : <span>Notifications</span>}
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Transition
|
<Transition
|
||||||
@ -117,6 +101,19 @@ export const NotificationPopover = () => {
|
|||||||
Notifications
|
Notifications
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex gap-x-2 justify-center items-center">
|
<div className="flex gap-x-2 justify-center items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
notificationsMutate();
|
||||||
|
const target = e.target as HTMLButtonElement;
|
||||||
|
target?.classList.add("animate-spin");
|
||||||
|
setTimeout(() => {
|
||||||
|
target?.classList.remove("animate-spin");
|
||||||
|
}, 1000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon iconName="refresh" className="h-6 w-6 text-custom-text-300" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -125,7 +122,7 @@ export const NotificationPopover = () => {
|
|||||||
setReadNotification((prev) => !prev);
|
setReadNotification((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SortIcon className="h-6 w-6 text-custom-text-300" />
|
<Icon iconName="filter_list" className="h-6 w-6 text-custom-text-300" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -135,7 +132,7 @@ export const NotificationPopover = () => {
|
|||||||
setSnoozed((prev) => !prev);
|
setSnoozed((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClockIcon className="h-6 w-6 text-custom-text-300" />
|
<Icon iconName="schedule" className="h-6 w-6 text-custom-text-300" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -145,10 +142,10 @@ export const NotificationPopover = () => {
|
|||||||
setArchived((prev) => !prev);
|
setArchived((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArchiveIcon className="h-6 w-6 text-custom-text-300" />
|
<Icon iconName="archive" className="h-6 w-6 text-custom-text-300" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={() => closePopover()}>
|
<button type="button" onClick={() => closePopover()}>
|
||||||
<XMarkIcon className="h-6 w-6 text-custom-text-300" />
|
<Icon iconName="close" className="h-6 w-6 text-custom-text-300" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -166,7 +163,7 @@ export const NotificationPopover = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h4 className="text-custom-text-300 text-center flex items-center">
|
<h4 className="text-custom-text-300 text-center flex items-center">
|
||||||
<ArrowLeftIcon className="h-5 w-5 text-custom-text-300" />
|
<Icon iconName="arrow_back" className="h-5 w-5 text-custom-text-300" />
|
||||||
<span className="ml-2 font-semibold">
|
<span className="ml-2 font-semibold">
|
||||||
{snoozed
|
{snoozed
|
||||||
? "Snoozed Notifications"
|
? "Snoozed Notifications"
|
||||||
@ -202,7 +199,9 @@ export const NotificationPopover = () => {
|
|||||||
|
|
||||||
<div className="w-full flex-1 overflow-y-auto">
|
<div className="w-full flex-1 overflow-y-auto">
|
||||||
{notifications ? (
|
{notifications ? (
|
||||||
notifications.length > 0 ? (
|
notifications.filter(
|
||||||
|
(notification) => notification.data.issue_activity.field !== "None"
|
||||||
|
).length > 0 ? (
|
||||||
notifications.map((notification) => (
|
notifications.map((notification) => (
|
||||||
<NotificationCard
|
<NotificationCard
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
@ -210,6 +209,7 @@ export const NotificationPopover = () => {
|
|||||||
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
||||||
markNotificationReadStatus={markNotificationReadStatus}
|
markNotificationReadStatus={markNotificationReadStatus}
|
||||||
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
||||||
|
markSnoozeNotification={markSnoozeNotification}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
@ -7,35 +7,37 @@ import { useRouter } from "next/router";
|
|||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
|
||||||
import { Transition, Dialog, Listbox } from "@headlessui/react";
|
import { Transition, Dialog, Listbox } from "@headlessui/react";
|
||||||
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
|
|
||||||
|
|
||||||
// date helper
|
// date helper
|
||||||
import { getDatesAfterCurrentDate, getTimestampAfterCurrentTime } from "helpers/date-time.helper";
|
import { getDatesAfterCurrentDate, getTimestampAfterCurrentTime } from "helpers/date-time.helper";
|
||||||
|
|
||||||
// services
|
|
||||||
import userNotificationServices from "services/notifications.service";
|
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
import { PrimaryButton, SecondaryButton, Icon } from "components/ui";
|
||||||
|
|
||||||
// icons
|
// types
|
||||||
import { XMarkIcon } from "components/icons";
|
import type { IUserNotification } from "types";
|
||||||
|
|
||||||
type SnoozeModalProps = {
|
type SnoozeModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
notificationId: string | null;
|
notification: IUserNotification | null;
|
||||||
|
onSubmit: (notificationId: string, dateTime?: Date | undefined) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dates = getDatesAfterCurrentDate();
|
const dates = getDatesAfterCurrentDate();
|
||||||
const timeStamps = getTimestampAfterCurrentTime();
|
const timeStamps = getTimestampAfterCurrentTime();
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
time: null,
|
||||||
|
date: null,
|
||||||
|
};
|
||||||
|
|
||||||
export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||||
const { isOpen, onClose, notificationId, onSuccess } = props;
|
const { isOpen, onClose, notification, onSuccess, onSubmit: handleSubmitSnooze } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -47,28 +49,26 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
reset,
|
reset,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
} = useForm<any>();
|
} = useForm<any>({
|
||||||
|
defaultValues,
|
||||||
|
});
|
||||||
|
|
||||||
const onSubmit = async (formData: any) => {
|
const onSubmit = async (formData: any) => {
|
||||||
if (!workspaceSlug || !notificationId) return;
|
if (!workspaceSlug || !notification) return;
|
||||||
|
|
||||||
const dateTime = new Date(
|
const dateTime = new Date(
|
||||||
`${formData.date.toLocaleDateString()} ${formData.time.toLocaleTimeString()}`
|
`${formData.date.toLocaleDateString()} ${formData.time.toLocaleTimeString()}`
|
||||||
);
|
);
|
||||||
|
|
||||||
await userNotificationServices
|
await handleSubmitSnooze(notification.id, dateTime).then(() => {
|
||||||
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
onClose();
|
||||||
snoozed_till: dateTime,
|
onSuccess();
|
||||||
})
|
setToastAlert({
|
||||||
.then(() => {
|
title: "Notification snoozed",
|
||||||
onClose();
|
message: "Notification snoozed successfully",
|
||||||
onSuccess();
|
type: "success",
|
||||||
setToastAlert({
|
|
||||||
title: "Notification snoozed",
|
|
||||||
message: "Notification snoozed successfully",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@ -91,7 +91,7 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
|
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||||
@ -117,7 +117,7 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="button">
|
<button type="button">
|
||||||
<XMarkIcon className="w-5 h-5 text-custom-text-100" />
|
<Icon iconName="close" className="w-5 h-5 text-custom-text-100" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,18 +133,21 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="relative mt-2">
|
<div className="relative mt-2">
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm sm:leading-6">
|
<Listbox.Button className="relative w-full cursor-default rounded-md border border-custom-border-100 bg-custom-background-100 py-1.5 pl-3 pr-10 text-left text-custom-text-100 shadow-sm focus:outline-none sm:text-sm sm:leading-6">
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<span className="ml-3 block truncate">
|
<span className="ml-3 block truncate">
|
||||||
{value?.toLocaleTimeString([], {
|
{value
|
||||||
hour: "2-digit",
|
? new Date(value)?.toLocaleTimeString([], {
|
||||||
minute: "2-digit",
|
hour: "2-digit",
|
||||||
}) || "Select Time"}
|
minute: "2-digit",
|
||||||
|
})
|
||||||
|
: "Select Time"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
||||||
<ChevronDownIcon
|
<Icon
|
||||||
className="h-5 w-5 text-gray-400"
|
iconName="expand_more"
|
||||||
|
className="h-5 w-5 text-custom-text-100"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -157,14 +160,14 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-custom-background-100 py-1 text-base shadow-lg focus:outline-none sm:text-sm">
|
||||||
{timeStamps.map((time, index) => (
|
{timeStamps.map((time, index) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={`${time.label}-${index}`}
|
key={`${time.label}-${index}`}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
||||||
active
|
active
|
||||||
? "bg-custom-primary-100 text-custom-text-100"
|
? "bg-custom-primary-100/80 text-custom-text-100"
|
||||||
: "text-custom-text-700"
|
: "text-custom-text-700"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@ -190,7 +193,8 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
: "text-custom-primary-100"
|
: "text-custom-primary-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<Icon
|
||||||
|
iconName="done"
|
||||||
className="h-5 w-5"
|
className="h-5 w-5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@ -219,19 +223,22 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="relative mt-2">
|
<div className="relative mt-2">
|
||||||
<Listbox.Button className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm sm:leading-6">
|
<Listbox.Button className="relative w-full cursor-default rounded-md border border-custom-border-100 bg-custom-background-100 py-1.5 pl-3 pr-10 text-left text-custom-text-100 shadow-sm focus:outline-none sm:text-sm sm:leading-6">
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<span className="ml-3 block truncate">
|
<span className="ml-3 block truncate">
|
||||||
{value?.toLocaleDateString([], {
|
{value
|
||||||
day: "numeric",
|
? new Date(value)?.toLocaleDateString([], {
|
||||||
month: "long",
|
day: "numeric",
|
||||||
year: "numeric",
|
month: "long",
|
||||||
}) || "Select Date"}
|
year: "numeric",
|
||||||
|
})
|
||||||
|
: "Select Date"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
||||||
<ChevronDownIcon
|
<Icon
|
||||||
className="h-5 w-5 text-gray-400"
|
iconName="expand_more"
|
||||||
|
className="h-5 w-5 text-custom-text-100"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -244,14 +251,14 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-custom-background-100 py-1 text-base shadow-lg focus:outline-none sm:text-sm">
|
||||||
{dates.map((date, index) => (
|
{dates.map((date, index) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={`${date.label}-${index}`}
|
key={`${date.label}-${index}`}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
||||||
active
|
active
|
||||||
? "bg-custom-primary-100 text-custom-text-100"
|
? "bg-custom-primary-100/80 text-custom-text-100"
|
||||||
: "text-custom-text-700"
|
: "text-custom-text-700"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@ -277,7 +284,8 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
: "text-custom-primary-100"
|
: "text-custom-primary-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<Icon
|
||||||
|
iconName="done"
|
||||||
className="h-5 w-5"
|
className="h-5 w-5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@ -302,7 +310,7 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
|||||||
<div className="w-full flex items-center gap-2 justify-end">
|
<div className="w-full flex items-center gap-2 justify-end">
|
||||||
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
||||||
<PrimaryButton type="submit" loading={isSubmitting}>
|
<PrimaryButton type="submit" loading={isSubmitting}>
|
||||||
Submit
|
{isSubmitting ? "Submitting..." : "Submit"}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
2991
apps/app/components/ui/icon-name-type.d.ts
vendored
Normal file
2991
apps/app/components/ui/icon-name-type.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,34 +5,32 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useTheme from "hooks/use-theme";
|
import useTheme from "hooks/use-theme";
|
||||||
// icons
|
|
||||||
import { ChartBarIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { GridViewIcon, AssignmentClipboardIcon, TickMarkIcon } from "components/icons";
|
|
||||||
|
|
||||||
import { NotificationPopover } from "components/notifications";
|
import { NotificationPopover } from "components/notifications";
|
||||||
|
|
||||||
const workspaceLinks = (workspaceSlug: string) => [
|
const workspaceLinks = (workspaceSlug: string) => [
|
||||||
{
|
{
|
||||||
icon: GridViewIcon,
|
icon: "grid_view",
|
||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
href: `/${workspaceSlug}`,
|
href: `/${workspaceSlug}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: ChartBarIcon,
|
icon: "bar_chart",
|
||||||
name: "Analytics",
|
name: "Analytics",
|
||||||
href: `/${workspaceSlug}/analytics`,
|
href: `/${workspaceSlug}/analytics`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: AssignmentClipboardIcon,
|
icon: "work",
|
||||||
name: "Projects",
|
name: "Projects",
|
||||||
href: `/${workspaceSlug}/projects`,
|
href: `/${workspaceSlug}/projects`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: TickMarkIcon,
|
icon: "task_alt",
|
||||||
name: "My Issues",
|
name: "My Issues",
|
||||||
href: `/${workspaceSlug}/me/my-issues`,
|
href: `/${workspaceSlug}/me/my-issues`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { Icon, Tooltip } from "components/ui";
|
import { Icon, Tooltip } from "components/ui";
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ export const getDatesAfterCurrentDate = (): Array<{
|
|||||||
const current = new Date();
|
const current = new Date();
|
||||||
const date = [];
|
const date = [];
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
const newDate = new Date(current.getTime() + i * 24 * 60 * 60 * 1000);
|
const newDate = new Date(Math.round(current.getTime() / (30 * 60 * 1000)) * 30 * 60 * 1000);
|
||||||
date.push({
|
date.push({
|
||||||
label: newDate.toLocaleDateString([], {
|
label: newDate.toLocaleDateString([], {
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ const useUserNotification = () => {
|
|||||||
type: selectedTab,
|
type: selectedTab,
|
||||||
snoozed,
|
snoozed,
|
||||||
archived,
|
archived,
|
||||||
read: readNotification,
|
read: selectedTab === null ? !readNotification : undefined,
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
workspaceSlug
|
workspaceSlug
|
||||||
@ -41,7 +41,7 @@ const useUserNotification = () => {
|
|||||||
type: selectedTab,
|
type: selectedTab,
|
||||||
snoozed,
|
snoozed,
|
||||||
archived,
|
archived,
|
||||||
read: readNotification,
|
read: selectedTab === null ? !readNotification : undefined,
|
||||||
})
|
})
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
@ -121,11 +121,39 @@ const useUserNotification = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const markSnoozeNotification = async (notificationId: string, dateTime?: Date) => {
|
||||||
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
|
const isSnoozed =
|
||||||
|
notifications?.find((notification) => notification.id === notificationId)?.snoozed_till !==
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (isSnoozed) {
|
||||||
|
await userNotificationServices
|
||||||
|
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
||||||
|
snoozed_till: null,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
notificationsMutate();
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
await userNotificationServices
|
||||||
|
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
||||||
|
snoozed_till: dateTime,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
notificationsMutate(
|
||||||
|
(prevData) => prevData?.filter((prev) => prev.id !== notificationId) || []
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notifications,
|
notifications,
|
||||||
notificationsMutate,
|
notificationsMutate,
|
||||||
markNotificationReadStatus,
|
markNotificationReadStatus,
|
||||||
markNotificationArchivedStatus,
|
markNotificationArchivedStatus,
|
||||||
|
markSnoozeNotification,
|
||||||
snoozed,
|
snoozed,
|
||||||
setSnoozed,
|
setSnoozed,
|
||||||
archived,
|
archived,
|
||||||
|
2
apps/app/types/notifications.d.ts
vendored
2
apps/app/types/notifications.d.ts
vendored
@ -46,7 +46,7 @@ export interface IIssueLite {
|
|||||||
state_group: string;
|
state_group: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationType = "created" | "assigned" | "watching";
|
export type NotificationType = "created" | "assigned" | "watching" | null;
|
||||||
|
|
||||||
export interface INotificationParams {
|
export interface INotificationParams {
|
||||||
snoozed?: boolean;
|
snoozed?: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user