import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form"; import {InfoIcon, Plus, Trash2} from "lucide-react"; import {useFieldArray} from "react-hook-form"; import {DatabaseWith} from "@/db/schema/07_database"; import {NotificationChannel} from "@/db/schema/09_notification-channel"; import {Label} from "@/components/ui/label"; import {Button} from "@/components/ui/button"; import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select"; import {useMutation} from "@tanstack/react-query"; import {toast} from "sonner"; import {useRouter} from "next/navigation"; import {Switch} from "@/components/ui/switch"; import {Card} from "@/components/ui/card"; import Link from "next/link"; import {useIsMobile} from "@/hooks/use-mobile"; import { ChannelKind, getChannelIcon, getChannelTextBasedOnKind } from "@/components/wrappers/dashboard/admin/channels/helpers/common"; import {StorageChannel} from "@/db/schema/12_storage-channel"; import { EVENT_KIND_OPTIONS, PoliciesSchema, PoliciesType, PolicyType } from "@/components/wrappers/dashboard/database/channels-policy/policy.schema"; import { createAlertPoliciesAction, createStoragePoliciesAction, deleteAlertPoliciesAction, deleteStoragePoliciesAction, updateAlertPoliciesAction, updateStoragePoliciesAction } from "@/components/wrappers/dashboard/database/channels-policy/policy.action"; type ChannelPoliciesFormProps = { onSuccess?: () => void; channels: NotificationChannel[] | StorageChannel[]; organizationId: string; database: DatabaseWith; kind: ChannelKind }; export const ChannelPoliciesForm = ({ database, channels, organizationId, onSuccess, kind }: ChannelPoliciesFormProps) => { const router = useRouter(); const isMobile = useIsMobile(); const channelText = getChannelTextBasedOnKind(kind); const organizationChannels = channels.map(c => c.id); const filterByChannel = ( items: T[] | undefined | null, channelKey: K ): T[] => items?.filter(item => organizationChannels.includes(item[channelKey] as string)) ?? []; const formattedAlertPolicies = filterByChannel(database.alertPolicies, "notificationChannelId") .map(({notificationChannelId, eventKinds, enabled}) => ({ channelId: notificationChannelId, eventKinds, enabled })); const formattedStoragePolicies = filterByChannel(database.storagePolicies, "storageChannelId") .map(({storageChannelId, enabled}) => ({ channelId: storageChannelId, enabled })); const defaultPolicies: PolicyType[] = kind === "notification" ? formattedAlertPolicies : formattedStoragePolicies.map(({ channelId, enabled }) => ({ channelId, enabled })); const form = useZodForm({ schema: PoliciesSchema, defaultValues: { policies: defaultPolicies }, context: { kind } }); const {fields, append, remove} = useFieldArray({ control: form.control, name: "policies" }); const addPolicy = () => append({channelId: "", eventKinds: [], enabled: true}); const removePolicyHandler = (index: number) => remove(index); const onCancel = () => { form.reset(); onSuccess?.(); }; const mutation = useMutation({ mutationFn: async ({policies}: PoliciesType) => { const payload = policies.map(p => kind === "notification" ? p : { ...p, eventKinds: undefined }); const policiesToAdd = payload.filter( (policy) => !defaultPolicies.some((a) => a.channelId === policy.channelId) ); const policiesToRemove = defaultPolicies.filter( (policy) => !payload.some((v) => v.channelId === policy.channelId) ); const policiesToUpdate = payload.filter((policy) => { const existing = defaultPolicies.find((a) => a.channelId === policy.channelId); return existing && (existing.eventKinds !== policy.eventKinds || existing.enabled !== policy.enabled); }); console.log(policiesToUpdate); console.log(policiesToAdd); const promises = kind === "notification" ? [ policiesToAdd.length > 0 ? await createAlertPoliciesAction({databaseId: database.id, alertPolicies: policiesToAdd}) : null, policiesToUpdate.length > 0 ? await updateAlertPoliciesAction({databaseId: database.id, alertPolicies: policiesToUpdate}) : null, policiesToRemove.length > 0 ? await deleteAlertPoliciesAction({databaseId: database.id, alertPolicies: policiesToRemove}) : null, ] : [ policiesToAdd.length > 0 ? await createStoragePoliciesAction({databaseId: database.id, storagePolicies: policiesToAdd}) : null, policiesToUpdate.length > 0 ? await updateStoragePoliciesAction({databaseId: database.id, storagePolicies: policiesToUpdate}) : null, policiesToRemove.length > 0 ? await deleteStoragePoliciesAction({databaseId: database.id, storagePolicies: policiesToRemove}) : null, ]; const results = await Promise.allSettled(promises); const rejected = results.find((r): r is PromiseRejectedResult => r.status === "rejected"); if (rejected) throw new Error(rejected.reason?.message || "Network or server error"); const failedActions = results .filter((r): r is PromiseFulfilledResult => r.status === "fulfilled") .map(r => r.value) .filter((v): v is { data: { success: false; actionError: any } } => v !== null && v.data?.success === false); if (failedActions.length > 0) throw new Error(failedActions[0].data.actionError?.message || "One or more operations failed"); return {success: true}; }, onSuccess: () => { toast.success("Policies saved successfully"); router.refresh(); }, onError: (error: any) => { toast.error(error.message || "Failed to save policies"); }, }); return (
{ if (kind === "notification") { for (const policy of values.policies) { if (!policy.eventKinds || policy.eventKinds.length === 0) { toast.error("Please select at least one event for all notification policies"); return; } } } await mutation.mutateAsync(values) } }>
{!isMobile && (

{kind === "notification" ? "Choose which channels receive notifications for specific events." : "Choose which storage to use with your database"}

)}
{channels.length === 0 ? (

No channels

Please configure {channelText.toLowerCase()} channels in your organization settings first.

) : fields.length === 0 ? (

No policies

{kind === "notification" ? `Click "Add Policy" to start receiving notifications.` : `Click "Add Policy" to use this storage.`}

) : (
{fields.map((field, index) => (
{ const selectedIds = form.watch("policies").map((a: PolicyType) => a.channelId).filter(Boolean); const availableChannels = channels.filter( (channel) => channel.id.toString() === field.value?.toString() || !selectedIds.includes(channel.id.toString()) ); const selectedChannel = channels.find(c => c.id === field.value); return ( ); }} />
(
{!isMobile && ( )}
)} />
{kind === "notification" && ( ( Trigger Events
)} /> )}
))}
)}
Cancel Save Changes
); };