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,
|
||||
} from "components/issues";
|
||||
// ui
|
||||
import { Input, Spinner, CustomDatePicker } from "components/ui";
|
||||
import { Input, Spinner, CustomDatePicker, Icon } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
TagIcon,
|
||||
@ -293,16 +293,19 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
||||
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
||||
</h4>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
type="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"
|
||||
onClick={() => {
|
||||
if (subscribed) handleUnsubscribe();
|
||||
else handleSubscribe();
|
||||
}}
|
||||
>
|
||||
{loading ? "Loading..." : subscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</button>
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
onClick={() => {
|
||||
if (subscribed) handleUnsubscribe();
|
||||
else handleSubscribe();
|
||||
}}
|
||||
>
|
||||
<Icon iconName="notifications" />
|
||||
{loading ? "Loading..." : subscribed ? "Unsubscribe" : "Subscribe"}
|
||||
</button>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
||||
<button
|
||||
type="button"
|
||||
|
@ -5,9 +5,10 @@ import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// hooks
|
||||
import useUserNotification from "hooks/use-user-notifications";
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
// icons
|
||||
import { ArchiveIcon, ClockIcon, SingleCommentCard } from "components/icons";
|
||||
import { Icon } from "components/ui";
|
||||
|
||||
// helper
|
||||
import { stripHTML, replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
@ -18,9 +19,10 @@ import type { IUserNotification } from "types";
|
||||
|
||||
type NotificationCardProps = {
|
||||
notification: IUserNotification;
|
||||
markNotificationReadStatus: (notificationId: string) => void;
|
||||
markNotificationArchivedStatus: (notificationId: string) => void;
|
||||
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) => {
|
||||
@ -29,11 +31,16 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
markNotificationReadStatus,
|
||||
markNotificationArchivedStatus,
|
||||
setSelectedNotificationForSnooze,
|
||||
markSnoozeNotification,
|
||||
} = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
if (notification.data.issue_activity.field === "None") return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={notification.id}
|
||||
@ -79,7 +86,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
{notification.triggered_by_details.first_name}{" "}
|
||||
{notification.triggered_by_details.last_name}{" "}
|
||||
</span>
|
||||
{notification.data.issue_activity.verb}{" "}
|
||||
{notification.data.issue_activity.field !== "comment" &&
|
||||
notification.data.issue_activity.verb}{" "}
|
||||
{notification.data.issue_activity.field !== "comment"
|
||||
? replaceUnderscoreIfSnakeCase(notification.data.issue_activity.field)
|
||||
: "commented"}{" "}
|
||||
@ -89,13 +97,17 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
{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 > 50
|
||||
{notification.data.issue_activity.new_value.length > 55
|
||||
? notification?.data?.issue_activity?.issue_comment?.slice(0, 50) + "..."
|
||||
: notification.data.issue_activity.issue_comment}
|
||||
{`"`}
|
||||
@ -122,25 +134,43 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
{
|
||||
id: 1,
|
||||
name: notification.read_at ? "Mark as Unread" : "Mark as Read",
|
||||
icon: SingleCommentCard,
|
||||
icon: "chat_bubble",
|
||||
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,
|
||||
name: notification.archived_at ? "Unarchive Notification" : "Archive Notification",
|
||||
icon: ArchiveIcon,
|
||||
icon: "archive",
|
||||
onClick: () => {
|
||||
markNotificationArchivedStatus(notification.id);
|
||||
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: ClockIcon,
|
||||
icon: "schedule",
|
||||
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) => (
|
||||
@ -153,7 +183,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
key={item.id}
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
|
@ -4,23 +4,14 @@ import Image from "next/image";
|
||||
|
||||
// hooks
|
||||
import useTheme from "hooks/use-theme";
|
||||
// icons
|
||||
import {
|
||||
XMarkIcon,
|
||||
ArchiveIcon,
|
||||
ClockIcon,
|
||||
SortIcon,
|
||||
BellNotificationIcon,
|
||||
} from "components/icons";
|
||||
|
||||
import { Popover, Transition, Menu } from "@headlessui/react";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
// hooks
|
||||
import useUserNotification from "hooks/use-user-notifications";
|
||||
|
||||
// components
|
||||
import { Spinner } from "components/ui";
|
||||
import { Spinner, Icon } from "components/ui";
|
||||
import { SnoozeNotificationModal, NotificationCard } from "components/notifications";
|
||||
|
||||
// type
|
||||
@ -60,6 +51,7 @@ export const NotificationPopover = () => {
|
||||
notificationsMutate,
|
||||
markNotificationArchivedStatus,
|
||||
markNotificationReadStatus,
|
||||
markSnoozeNotification,
|
||||
} = useUserNotification();
|
||||
|
||||
// theme context
|
||||
@ -70,7 +62,12 @@ export const NotificationPopover = () => {
|
||||
<SnoozeNotificationModal
|
||||
isOpen={selectedNotificationForSnooze !== null}
|
||||
onClose={() => setSelectedNotificationForSnooze(null)}
|
||||
notificationId={selectedNotificationForSnooze}
|
||||
onSubmit={markSnoozeNotification}
|
||||
notification={
|
||||
notifications?.find(
|
||||
(notification) => notification.id === selectedNotificationForSnooze
|
||||
) || null
|
||||
}
|
||||
onSuccess={() => {
|
||||
notificationsMutate();
|
||||
setSelectedNotificationForSnooze(null);
|
||||
@ -80,26 +77,13 @@ export const NotificationPopover = () => {
|
||||
{({ open: isActive, close: closePopover }) => (
|
||||
<>
|
||||
<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
|
||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90"
|
||||
} group flex w-full items-center gap-3 rounded-md p-2 text-sm font-medium outline-none ${
|
||||
sidebarCollapse ? "justify-center" : ""
|
||||
}`}
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||
>
|
||||
<span className="grid h-5 w-5 flex-shrink-0 place-items-center">
|
||||
<BellNotificationIcon
|
||||
color={
|
||||
isActive
|
||||
? "rgb(var(--color-sidebar-text-100))"
|
||||
: "rgb(var(--color-sidebar-text-200))"
|
||||
}
|
||||
aria-hidden="true"
|
||||
height="20"
|
||||
width="20"
|
||||
/>
|
||||
</span>
|
||||
<Icon iconName="notifications" />
|
||||
{sidebarCollapse ? null : <span>Notifications</span>}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
@ -117,6 +101,19 @@ export const NotificationPopover = () => {
|
||||
Notifications
|
||||
</h2>
|
||||
<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
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@ -125,7 +122,7 @@ export const NotificationPopover = () => {
|
||||
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
|
||||
type="button"
|
||||
@ -135,7 +132,7 @@ export const NotificationPopover = () => {
|
||||
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
|
||||
type="button"
|
||||
@ -145,10 +142,10 @@ export const NotificationPopover = () => {
|
||||
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 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>
|
||||
</div>
|
||||
</div>
|
||||
@ -166,7 +163,7 @@ export const NotificationPopover = () => {
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
{snoozed
|
||||
? "Snoozed Notifications"
|
||||
@ -202,7 +199,9 @@ export const NotificationPopover = () => {
|
||||
|
||||
<div className="w-full flex-1 overflow-y-auto">
|
||||
{notifications ? (
|
||||
notifications.length > 0 ? (
|
||||
notifications.filter(
|
||||
(notification) => notification.data.issue_activity.field !== "None"
|
||||
).length > 0 ? (
|
||||
notifications.map((notification) => (
|
||||
<NotificationCard
|
||||
key={notification.id}
|
||||
@ -210,6 +209,7 @@ export const NotificationPopover = () => {
|
||||
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
||||
markNotificationReadStatus={markNotificationReadStatus}
|
||||
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
||||
markSnoozeNotification={markSnoozeNotification}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
@ -7,35 +7,37 @@ import { useRouter } from "next/router";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
|
||||
import { Transition, Dialog, Listbox } from "@headlessui/react";
|
||||
import { ChevronDownIcon, CheckIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
// date helper
|
||||
import { getDatesAfterCurrentDate, getTimestampAfterCurrentTime } from "helpers/date-time.helper";
|
||||
|
||||
// services
|
||||
import userNotificationServices from "services/notifications.service";
|
||||
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
// components
|
||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
||||
import { PrimaryButton, SecondaryButton, Icon } from "components/ui";
|
||||
|
||||
// icons
|
||||
import { XMarkIcon } from "components/icons";
|
||||
// types
|
||||
import type { IUserNotification } from "types";
|
||||
|
||||
type SnoozeModalProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
notificationId: string | null;
|
||||
notification: IUserNotification | null;
|
||||
onSubmit: (notificationId: string, dateTime?: Date | undefined) => Promise<void>;
|
||||
};
|
||||
|
||||
const dates = getDatesAfterCurrentDate();
|
||||
const timeStamps = getTimestampAfterCurrentTime();
|
||||
|
||||
const defaultValues = {
|
||||
time: null,
|
||||
date: null,
|
||||
};
|
||||
|
||||
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 { workspaceSlug } = router.query;
|
||||
@ -47,28 +49,26 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
reset,
|
||||
handleSubmit,
|
||||
control,
|
||||
} = useForm<any>();
|
||||
} = useForm<any>({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const onSubmit = async (formData: any) => {
|
||||
if (!workspaceSlug || !notificationId) return;
|
||||
if (!workspaceSlug || !notification) return;
|
||||
|
||||
const dateTime = new Date(
|
||||
`${formData.date.toLocaleDateString()} ${formData.time.toLocaleTimeString()}`
|
||||
);
|
||||
|
||||
await userNotificationServices
|
||||
.patchUserNotification(workspaceSlug.toString(), notificationId, {
|
||||
snoozed_till: dateTime,
|
||||
})
|
||||
.then(() => {
|
||||
onClose();
|
||||
onSuccess();
|
||||
setToastAlert({
|
||||
title: "Notification snoozed",
|
||||
message: "Notification snoozed successfully",
|
||||
type: "success",
|
||||
});
|
||||
await handleSubmitSnooze(notification.id, dateTime).then(() => {
|
||||
onClose();
|
||||
onSuccess();
|
||||
setToastAlert({
|
||||
title: "Notification snoozed",
|
||||
message: "Notification snoozed successfully",
|
||||
type: "success",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@ -91,7 +91,7 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
leaveFrom="opacity-100"
|
||||
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>
|
||||
|
||||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||
@ -117,7 +117,7 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -133,18 +133,21 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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="ml-3 block truncate">
|
||||
{value?.toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}) || "Select Time"}
|
||||
{value
|
||||
? new Date(value)?.toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "Select Time"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
<Icon
|
||||
iconName="expand_more"
|
||||
className="h-5 w-5 text-custom-text-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
@ -157,14 +160,14 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
leaveFrom="opacity-100"
|
||||
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) => (
|
||||
<Listbox.Option
|
||||
key={`${time.label}-${index}`}
|
||||
className={({ active }) =>
|
||||
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
||||
active
|
||||
? "bg-custom-primary-100 text-custom-text-100"
|
||||
? "bg-custom-primary-100/80 text-custom-text-100"
|
||||
: "text-custom-text-700"
|
||||
}`
|
||||
}
|
||||
@ -190,7 +193,8 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
: "text-custom-primary-100"
|
||||
}`}
|
||||
>
|
||||
<CheckIcon
|
||||
<Icon
|
||||
iconName="done"
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
@ -219,19 +223,22 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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="ml-3 block truncate">
|
||||
{value?.toLocaleDateString([], {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}) || "Select Date"}
|
||||
{value
|
||||
? new Date(value)?.toLocaleDateString([], {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
: "Select Date"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
<Icon
|
||||
iconName="expand_more"
|
||||
className="h-5 w-5 text-custom-text-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
@ -244,14 +251,14 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
leaveFrom="opacity-100"
|
||||
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) => (
|
||||
<Listbox.Option
|
||||
key={`${date.label}-${index}`}
|
||||
className={({ active }) =>
|
||||
`relative cursor-default select-none py-2 pl-3 pr-9 ${
|
||||
active
|
||||
? "bg-custom-primary-100 text-custom-text-100"
|
||||
? "bg-custom-primary-100/80 text-custom-text-100"
|
||||
: "text-custom-text-700"
|
||||
}`
|
||||
}
|
||||
@ -277,7 +284,8 @@ export const SnoozeNotificationModal: React.FC<SnoozeModalProps> = (props) => {
|
||||
: "text-custom-primary-100"
|
||||
}`}
|
||||
>
|
||||
<CheckIcon
|
||||
<Icon
|
||||
iconName="done"
|
||||
className="h-5 w-5"
|
||||
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">
|
||||
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
||||
<PrimaryButton type="submit" loading={isSubmitting}>
|
||||
Submit
|
||||
{isSubmitting ? "Submitting..." : "Submit"}
|
||||
</PrimaryButton>
|
||||
</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
|
||||
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";
|
||||
|
||||
const workspaceLinks = (workspaceSlug: string) => [
|
||||
{
|
||||
icon: GridViewIcon,
|
||||
icon: "grid_view",
|
||||
name: "Dashboard",
|
||||
href: `/${workspaceSlug}`,
|
||||
},
|
||||
{
|
||||
icon: ChartBarIcon,
|
||||
icon: "bar_chart",
|
||||
name: "Analytics",
|
||||
href: `/${workspaceSlug}/analytics`,
|
||||
},
|
||||
{
|
||||
icon: AssignmentClipboardIcon,
|
||||
icon: "work",
|
||||
name: "Projects",
|
||||
href: `/${workspaceSlug}/projects`,
|
||||
},
|
||||
{
|
||||
icon: TickMarkIcon,
|
||||
icon: "task_alt",
|
||||
name: "My Issues",
|
||||
href: `/${workspaceSlug}/me/my-issues`,
|
||||
},
|
||||
];
|
||||
|
||||
// components
|
||||
import { Icon, Tooltip } from "components/ui";
|
||||
|
||||
|
@ -310,7 +310,7 @@ export const getDatesAfterCurrentDate = (): Array<{
|
||||
const current = new Date();
|
||||
const date = [];
|
||||
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({
|
||||
label: newDate.toLocaleDateString([], {
|
||||
day: "numeric",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@ -32,7 +32,7 @@ const useUserNotification = () => {
|
||||
type: selectedTab,
|
||||
snoozed,
|
||||
archived,
|
||||
read: readNotification,
|
||||
read: selectedTab === null ? !readNotification : undefined,
|
||||
})
|
||||
: null,
|
||||
workspaceSlug
|
||||
@ -41,7 +41,7 @@ const useUserNotification = () => {
|
||||
type: selectedTab,
|
||||
snoozed,
|
||||
archived,
|
||||
read: readNotification,
|
||||
read: selectedTab === null ? !readNotification : undefined,
|
||||
})
|
||||
: 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 {
|
||||
notifications,
|
||||
notificationsMutate,
|
||||
markNotificationReadStatus,
|
||||
markNotificationArchivedStatus,
|
||||
markSnoozeNotification,
|
||||
snoozed,
|
||||
setSnoozed,
|
||||
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;
|
||||
}
|
||||
|
||||
export type NotificationType = "created" | "assigned" | "watching";
|
||||
export type NotificationType = "created" | "assigned" | "watching" | null;
|
||||
|
||||
export interface INotificationParams {
|
||||
snoozed?: boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user