Working on UX/UI of the notification system.

This commit is contained in:
charlesgauthereau
2025-11-22 19:36:31 +01:00
parent e886bdd58d
commit eb471c7cdb
5 changed files with 164 additions and 52 deletions
@@ -1,31 +1,72 @@
// import Link from "next/link";
// import {cn} from "@/lib/utils";
// import {Plus} from "lucide-react";
//
// type EmptyStatePlaceholderProps = {
// url?: string;
// text: string;
// className?: string;
// }
//
// export const EmptyStatePlaceholder = ({url, text, className}: EmptyStatePlaceholderProps) => {
// return (
// <div className={cn("",className)}>{url ?
// <Link
// href={url}
// className={cn(
// "h-full",
// "flex flex-col items-center justify-center w-full rounded-2xl border border-dashed border-muted p-6 lg:p-10",
// "hover:bg-muted/50 transition-colors text-muted-foreground hover:text-primary text-center space-y-2"
// )}
// >
// <Plus className="w-5 h-5 lg:w-6 lg:h-6"/>
// <span className="text-sm lg:text-base font-medium">{text}</span>
// </Link>
// :
// <div className="flex h-full flex-col items-center justify-center py-12 text-center">
// <p className="text-lg text-muted-foreground">{text}</p>
// </div>
// }
// </div>
//
// )
// }
import Link from "next/link"; import Link from "next/link";
import {cn} from "@/lib/utils"; import {cn} from "@/lib/utils";
import {Plus} from "lucide-react"; import {Plus} from "lucide-react";
type EmptyStatePlaceholderProps = { type EmptyStatePlaceholderProps = {
url?: string; url?: string;
onClick?: () => void;
text: string; text: string;
} className?: string;
};
export const EmptyStatePlaceholder = ({url, text}: EmptyStatePlaceholderProps) => { export const EmptyStatePlaceholder = ({
return ( url,
<>{url ? onClick,
<Link text,
href={url} className,
className={cn( }: EmptyStatePlaceholderProps) => {
"flex flex-col items-center justify-center w-full rounded-2xl border border-dashed border-muted p-6 lg:p-10", const content = (
"hover:bg-muted/50 transition-colors text-muted-foreground hover:text-primary text-center space-y-2" <div
)} className={cn(
> "flex h-full flex-col items-center justify-center w-full rounded-2xl border border-dashed border-muted p-6 lg:p-10",
<Plus className="w-5 h-5 lg:w-6 lg:h-6"/> "hover:bg-muted/50 transition-colors text-muted-foreground hover:text-primary text-center space-y-4 cursor-pointer",
<span className="text-sm lg:text-base font-medium">{text}</span> onClick || url ? "cursor-pointer" : "cursor-default"
</Link> )}
: {...(onClick ? {onClick} : url ? {asChild: true} : {})}
<div className="flex flex-col items-center justify-center py-12 text-center"> >
<p className="text-lg text-muted-foreground">{text}</p> <Plus className="w-5 h-5 lg:w-6 lg:h-6"/>
<div>
<p className="text-sm ">{text}</p>
</div> </div>
} </div>
</> );
) return (
} <div className={cn("", className)}>
{url ? <Link href={url}>{content}</Link> : content}
</div>
);
};
@@ -14,17 +14,22 @@ import {NotificationChannel} from "@/db/schema/09_notification-channel";
type OrganizationNotifierAddModalProps = { type OrganizationNotifierAddModalProps = {
notificationChannel?: NotificationChannel notificationChannel?: NotificationChannel
organization?: OrganizationWithMembers; organization?: OrganizationWithMembers;
open: boolean;
onOpenChangeAction: (open: boolean) => void;
} }
export const NotifierAddEditModal = ({organization, notificationChannel}: OrganizationNotifierAddModalProps) => { export const NotifierAddEditModal = ({
organization,
notificationChannel,
open,
onOpenChangeAction
}: OrganizationNotifierAddModalProps) => {
const isCreate = !Boolean(notificationChannel); const isCreate = !Boolean(notificationChannel);
const [open, setOpen] = useState(false);
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={onOpenChangeAction}>
<DialogTrigger asChild> <DialogTrigger asChild>
{isCreate ? {isCreate ?
<Button> <Button>
@@ -49,7 +54,7 @@ export const NotifierAddEditModal = ({organization, notificationChannel}: Organi
<NotifierForm <NotifierForm
defaultValues={notificationChannel} defaultValues={notificationChannel}
organization={organization} organization={organization}
onSuccessAction={() => setOpen(false)} onSuccessAction={() => onOpenChangeAction(false)}
/> />
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -8,6 +8,7 @@ import {
updateNotificationChannelAction updateNotificationChannelAction
} from "@/components/wrappers/dashboard/common/notifier/notifier-form/notifier-form.action"; } from "@/components/wrappers/dashboard/common/notifier/notifier-form/notifier-form.action";
import {toast} from "sonner"; import {toast} from "sonner";
import {useState} from "react";
export type EditNotifierButtonProps = { export type EditNotifierButtonProps = {
notificationChannel: NotificationChannel; notificationChannel: NotificationChannel;
@@ -15,6 +16,8 @@ export type EditNotifierButtonProps = {
export const EditNotifierButton = ({notificationChannel}: EditNotifierButtonProps) => { export const EditNotifierButton = ({notificationChannel}: EditNotifierButtonProps) => {
const router = useRouter(); const router = useRouter();
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async (value: boolean) => { mutationFn: async (value: boolean) => {
@@ -49,6 +52,8 @@ export const EditNotifierButton = ({notificationChannel}: EditNotifierButtonProp
/> />
<NotifierAddEditModal <NotifierAddEditModal
notificationChannel={notificationChannel} notificationChannel={notificationChannel}
open={isAddModalOpen}
onOpenChangeAction={setIsAddModalOpen}
/> />
</> </>
); );
@@ -45,7 +45,6 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
}); });
const mutationCreateOrganisation = useMutation({ const mutationCreateOrganisation = useMutation({
mutationFn: async (values: NotificationChannelFormType) => { mutationFn: async (values: NotificationChannelFormType) => {
@@ -61,11 +60,11 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
if (inner?.success) { if (inner?.success) {
toast.success(inner.actionSuccess?.message); toast.success(inner.actionSuccess?.message);
// onSuccessAction?.(); onSuccessAction?.();
router.refresh(); router.refresh();
} else { } else {
toast.error(inner?.actionError?.message); toast.error(inner?.actionError?.message);
// onSuccessAction?.(); onSuccessAction?.();
} }
} }
}); });
@@ -134,7 +133,7 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
<div className="flex gap-4 justify-end"> <div className="flex gap-4 justify-end">
{defaultValues && ( {defaultValues && (
<NotifierTestChannelButton notificationChannel={defaultValues} /> <NotifierTestChannelButton notificationChannel={defaultValues}/>
)} )}
@@ -1,40 +1,102 @@
// import {OrganizationWithMembers} from "@/db/schema/03_organization";
// import {NotificationChannel} from "@/db/schema/09_notification-channel";
// import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
// import {NotifierCard} from "@/components/wrappers/dashboard/common/notifier/notifier-card/notifier-card";
// import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal";
// import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
//
//
// export type OrganizationNotifiersTabProps = {
// organization: OrganizationWithMembers;
// notificationChannels: NotificationChannel[];
// };
//
// export const OrganizationNotifiersTab = ({organization, notificationChannels}: OrganizationNotifiersTabProps) => {
//
// return (
// <div className="flex flex-col gap-y-4 h-full py-4">
// <EmptyStatePlaceholder className="h-full" text="No project available"/>
//
// <div className="flex flex-row gap-y-4 justify-between ">
// <div>
// <div className="flex items-center gap-3 mb-1">
// <h3 className="text-xl font-semibold text-balance">Notification Settings</h3>
// </div>
// <p className="text-muted-foreground leading-relaxed">
// Configure how and when you receive alerts about your services and infrastructure.
// </p>
// </div>
// <div>
// <NotifierAddEditModal organization={organization}/>
// </div>
// </div>
// <div className=" h-full">
// <CardsWithPagination data={notificationChannels} cardItem={NotifierCard} cardsPerPage={8}
// numberOfColumns={2} organization={organization}/>
// </div>
// </div>
// );
// };
import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {Bell} from "lucide-react";
import {NotificationChannel} from "@/db/schema/09_notification-channel"; import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination"; import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {NotifierCard} from "@/components/wrappers/dashboard/common/notifier/notifier-card/notifier-card"; import {NotifierCard} from "@/components/wrappers/dashboard/common/notifier/notifier-card/notifier-card";
import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal"; import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {useState} from "react";
import {cn} from "@/lib/utils";
export type OrganizationNotifiersTabProps = { export type OrganizationNotifiersTabProps = {
organization: OrganizationWithMembers; organization: OrganizationWithMembers;
notificationChannels: NotificationChannel[]; notificationChannels: NotificationChannel[];
}; };
export const OrganizationNotifiersTab = ({organization ,notificationChannels}: OrganizationNotifiersTabProps) => { export const OrganizationNotifiersTab = ({
organization,
notificationChannels,
}: OrganizationNotifiersTabProps) => {
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
// const hasNotifiers = 0;
const hasNotifiers = notificationChannels.length > 0;
return ( return (
<div className="flex flex-col gap-y-4 h-full py-4"> <div className="flex flex-col gap-y-6 h-full py-4">
<div className="flex flex-col gap-y-4 ">
<div> <div className="h-full flex flex-col gap-y-6">
<div className="flex items-center gap-3 mb-2"> <div className={cn("hidden flex-row justify-between items-start", hasNotifiers && "flex")}>
<div <div className="max-w-2xl">
className="flex h-10 w-10 items-center justify-center rounded-lg bg-card border border-border"> <h3 className="text-xl font-semibold text-balance mb-1">
<Bell className="h-5 w-5 text-foreground"/> Notification Settings
</div> </h3>
<h1 className="text-3xl font-semibold text-balance">Notification Settings</h1> <p className="text-muted-foreground leading-relaxed">
Configure how and when you receive alerts about your services and
infrastructure.
</p>
</div> </div>
<p className="text-muted-foreground leading-relaxed"> <NotifierAddEditModal
Configure how and when you receive alerts about your services and infrastructure. organization={organization}
</p> open={isAddModalOpen}
onOpenChangeAction={setIsAddModalOpen}
/>
</div> </div>
<div> {hasNotifiers ? (
<NotifierAddEditModal organization={organization}/> <div className="h-full">
</div> <CardsWithPagination
</div> data={notificationChannels}
<div className=" h-full"> cardItem={NotifierCard}
<CardsWithPagination data={notificationChannels} cardItem={NotifierCard} cardsPerPage={8} numberOfColumns={2} organization={organization}/> cardsPerPage={8}
numberOfColumns={2}
organization={organization}
/>
</div>
) : (
<EmptyStatePlaceholder
text="No notification channels configured yet"
onClick={() => setIsAddModalOpen(true)}
className="h-full"
/>
)}
</div> </div>
</div> </div>
); );