Working on notification system.

This commit is contained in:
charlesgauthereau
2025-12-06 23:14:59 +01:00
parent 9458b12d69
commit fde7688485
64 changed files with 792 additions and 551 deletions
@@ -8,33 +8,47 @@ import {
import {Button} from "@/components/ui/button";
import {NotifierForm} from "@/components/wrappers/dashboard/common/notifier/notifier-form/notifier-form";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {useIsMobile} from "@/hooks/use-mobile";
import {useState} from "react";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {
NotifierChannelOrganisationForm
} from "@/components/wrappers/dashboard/admin/notifications/channels/organization/notification-channels-organization-form";
type OrganizationNotifierAddModalProps = {
notificationChannel?: NotificationChannel
notificationChannel?: NotificationChannelWith
organization?: OrganizationWithMembers;
open: boolean;
onOpenChangeAction: (open: boolean) => void;
open?: boolean;
onOpenChangeAction?: (open: boolean) => void;
adminView?: boolean;
organizations?: OrganizationWithMembers[]
}
export const NotifierAddEditModal = ({
organization,
notificationChannel,
open,
onOpenChangeAction
open = false,
onOpenChangeAction,
adminView,
organizations
}: OrganizationNotifierAddModalProps) => {
const isMobile = useIsMobile();
const [openInternal, setOpen] = useState(open);
const isCreate = !Boolean(notificationChannel);
return (
<Dialog open={open} onOpenChange={onOpenChangeAction}>
<Dialog open={openInternal} onOpenChange={(state) => {
onOpenChangeAction?.(state);
setOpen(state);
}}>
<DialogTrigger asChild>
{isCreate ?
<Button>
<Plus/>{!isMobile && `Add notification channel` }
<Plus/>{!isMobile && `Add notification channel`}
</Button>
:
<Button
@@ -52,11 +66,52 @@ export const NotifierAddEditModal = ({
Configure your notification channel preferences and event triggers.
</DialogDescription>
</DialogHeader>
<NotifierForm
defaultValues={notificationChannel}
organization={organization}
onSuccessAction={() => onOpenChangeAction(false)}
/>
<div>
{adminView ?
<Tabs className="flex flex-col flex-1" defaultValue="configuration">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="configuration">Configuration</TabsTrigger>
<TabsTrigger value="organizations">Organizations</TabsTrigger>
</TabsList>
<TabsContent className="h-full justify-between" value="configuration">
<NotifierForm
adminView={adminView}
defaultValues={notificationChannel}
organization={organization}
onSuccessAction={() => {
onOpenChangeAction?.(false)
setOpen(false);
}}
/>
</TabsContent>
<TabsContent className="h-full justify-between" value="organizations">
<NotifierChannelOrganisationForm
defaultValues={notificationChannel}
organizations={organizations}
/>
</TabsContent>
</Tabs>
:
<NotifierForm
adminView={adminView}
defaultValues={notificationChannel}
organization={organization}
onSuccessAction={() => {
onOpenChangeAction?.(false)
setOpen(false);
}}
/>
}
</div>
</DialogContent>
</Dialog>
)
@@ -2,7 +2,7 @@
import {useMutation} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {Switch} from "@/components/ui/switch";
import {
updateNotificationChannelAction
@@ -12,11 +12,18 @@ import {useState} from "react";
import {Organization, OrganizationWithMembers} from "@/db/schema/03_organization";
export type EditNotifierButtonProps = {
notificationChannel: NotificationChannel;
organization? : OrganizationWithMembers;
notificationChannel: NotificationChannelWith;
organization?: OrganizationWithMembers;
organizations?: OrganizationWithMembers[];
adminView?: boolean;
};
export const EditNotifierButton = ({notificationChannel, organization}: EditNotifierButtonProps) => {
export const EditNotifierButton = ({
organizations,
adminView = false,
notificationChannel,
organization
}: EditNotifierButtonProps) => {
const router = useRouter();
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
@@ -53,6 +60,8 @@ export const EditNotifierButton = ({notificationChannel, organization}: EditNoti
}}
/>
<NotifierAddEditModal
organizations={organizations}
adminView={adminView}
organization={organization}
notificationChannel={notificationChannel}
open={isAddModalOpen}
@@ -1,19 +1,20 @@
"use client";
import {Card} from "@/components/ui/card";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {Mail, MessageSquare, Pencil, Trash2, Webhook} from "lucide-react";
import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {Badge} from "@/components/ui/badge";
import {Switch} from "@/components/ui/switch";
import {
DeleteNotifierButton
} from "@/components/wrappers/dashboard/common/notifier/notifier-card/button-delete-notifier";
import {EditNotifierButton} from "@/components/wrappers/dashboard/common/notifier/notifier-card/button-edit-notifier";
import {Organization, OrganizationWithMembers} from "@/db/schema/03_organization";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {getNotificationChannelIcon} from "@/components/wrappers/dashboard/admin/notifications/helpers";
export type NotifierCardProps = {
data: NotificationChannel;
data: NotificationChannelWith;
organization?: OrganizationWithMembers;
organizations?: OrganizationWithMembers[];
adminView?: boolean;
};
export const NotifierCard = (props: NotifierCardProps) => {
@@ -25,7 +26,7 @@ export const NotifierCard = (props: NotifierCardProps) => {
<div className="flex items-center gap-3">
<div
className="flex h-10 w-10 items-center justify-center rounded-md bg-secondary border border-border">
{getIcon(data.provider)}
{getNotificationChannelIcon(data.provider)}
</div>
<div className={`h-2 w-2 rounded-full ${data.enabled ? "bg-green-600" : "bg-muted"}`}/>
</div>
@@ -41,6 +42,8 @@ export const NotifierCard = (props: NotifierCardProps) => {
<div className="flex items-center gap-2">
<EditNotifierButton
organizations={props.organizations}
adminView={props.adminView}
organization={organization}
notificationChannel={data}/>
<DeleteNotifierButton
@@ -54,14 +57,3 @@ export const NotifierCard = (props: NotifierCardProps) => {
};
const getIcon = (type: string) => {
const Icon = notificationTypes.find((t) => t.value === type)?.icon
return Icon ? <Icon className="h-4 w-4"/> : null
}
const notificationTypes = [
{value: "smtp", label: "Email", icon: Mail},
{value: "slack", label: "Slack", icon: MessageSquare},
{value: "webhook", label: "Webhook", icon: Webhook},
]
@@ -7,7 +7,7 @@ export const NotificationChannelFormSchema = z.object({
.min(5, "Name must be at least 5 characters long")
.max(40, "Name must be at most 40 characters long"),
provider: z.enum(["curl", "slack", "smtp", "webhook"], {
provider: z.enum(["slack", "smtp"], {
required_error: "Provider is required",
}),
@@ -21,7 +21,7 @@ import {
NotifierSlackForm
} from "@/components/wrappers/dashboard/common/notifier/notifier-form/providers/notifier-slack.form";
import {Button} from "@/components/ui/button";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {
NotifierTestChannelButton
} from "@/components/wrappers/dashboard/common/notifier/notifier-form/notifier-test-channel-button";
@@ -30,13 +30,16 @@ import {useEffect} from "react";
type NotifierFormProps = {
onSuccessAction?: () => void;
organization?: OrganizationWithMembers;
defaultValues?: NotificationChannel
defaultValues?: NotificationChannelWith
adminView?: boolean
};
export const NotifierForm = ({onSuccessAction, organization, defaultValues}: NotifierFormProps) => {
export const NotifierForm = ({onSuccessAction, organization, defaultValues, adminView}: NotifierFormProps) => {
const isCreate = !Boolean(defaultValues);
const router = useRouter();
const isCreate = !Boolean(defaultValues);
const form = useZodForm({
schema: NotificationChannelFormSchema,
// @ts-ignore
@@ -47,7 +50,7 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
form.reset(defaultValues ? {...defaultValues} : {});
}, [defaultValues]);
const mutationCreateOrganisation = useMutation({
const mutationAddNotificationChannel = useMutation({
mutationFn: async (values: NotificationChannelFormType) => {
const payload = {
@@ -73,12 +76,14 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
const provider = form.watch("provider");
return (
<Form
form={form}
className="flex flex-col gap-4"
onSubmit={async (values) => {
await mutationCreateOrganisation.mutateAsync(values);
await mutationAddNotificationChannel.mutateAsync(values);
}}
>
<FormField
@@ -136,7 +141,8 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
<div className="flex justify-between">
<div>
{defaultValues && (
<NotifierTestChannelButton organizationId={organization?.id} notificationChannel={defaultValues}/>
<NotifierTestChannelButton organizationId={organization?.id}
notificationChannel={defaultValues}/>
)}
</div>
<div className="flex gap-2">
@@ -150,7 +156,7 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
>
Cancel
</Button>
<ButtonWithLoading isPending={mutationCreateOrganisation.isPending}>
<ButtonWithLoading isPending={mutationAddNotificationChannel.isPending}>
{isCreate ? "Add" : "Save"} Channel
</ButtonWithLoading>
</div>
@@ -159,7 +165,3 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
</Form>
);
};
@@ -7,7 +7,7 @@ import {
Layers,
ChartArea,
ShieldHalf,
Building, UserRoundCog, Mail, PackageOpen
Building, UserRoundCog, Mail, PackageOpen, Logs, Megaphone, Blocks
} from "lucide-react";
import {SidebarGroupItem, SidebarMenuCustomBase} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar";
import {authClient, useSession} from "@/lib/auth/auth-client";
@@ -69,6 +69,17 @@ export const SidebarMenuCustomMain = () => {
details: true,
type: "item"
},
{
title: "Notifications",
url: "/notifications",
icon: Megaphone,
details: true,
type: "collapse",
submenu: [
{ title: "Channels", url: "/notifications/channels", icon: Blocks, type: "item" },
{ title: "Activity Logs", url: "/notifications/logs", icon: Logs, type: "item" },
],
},
{
title: "Access management",
url: "/admin",