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:
Dakshesh Jain 2023-07-14 19:23:56 +05:30
parent be39b097ab
commit 7045f80ec6
9 changed files with 3176 additions and 118 deletions

View File

@ -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"

View File

@ -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>

View File

@ -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}
/> />
)) ))
) : ( ) : (

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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";

View File

@ -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",

View File

@ -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,

View File

@ -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;