fix: broken add notifications rule

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-02-27 09:37:43 +01:00
parent 8b18b86deb
commit a42adcdbfb
2 changed files with 84 additions and 83 deletions

View File

@@ -1,4 +1,3 @@
import { cn } from '@/utils/cn';
import { Slot } from '@radix-ui/react-slot';
import { Link, type LinkComponentProps } from '@tanstack/react-router';
import type { VariantProps } from 'class-variance-authority';
@@ -6,9 +5,10 @@ import { cva } from 'class-variance-authority';
import type { LucideIcon } from 'lucide-react';
import * as React from 'react';
import { Spinner, type SpinnerProps } from './spinner';
import { cn } from '@/utils/cn';
const buttonVariants = cva(
'inline-flex flex-shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:translate-y-[-0.5px] transition-all',
'inline-flex flex-shrink-0 select-none items-center justify-center whitespace-nowrap rounded-md font-medium ring-offset-background transition-all hover:translate-y-[-0.5px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
@@ -33,7 +33,7 @@ const buttonVariants = cva(
variant: 'default',
size: 'sm',
},
},
}
);
export interface ButtonProps
@@ -52,7 +52,10 @@ export interface ButtonProps
function fixHeight({
autoHeight,
size,
}: { autoHeight?: boolean; size: ButtonProps['size'] }) {
}: {
autoHeight?: boolean;
size: ButtonProps['size'];
}) {
if (autoHeight) {
switch (size) {
case 'lg':
@@ -84,9 +87,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
responsive,
autoHeight,
loadingAbsolute,
type = 'button',
...props
},
ref,
ref
) => {
const Comp = asChild ? Slot : 'button';
const Icon = loading ? null : (icon ?? null);
@@ -99,31 +103,32 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
className={cn(
buttonVariants({ variant, size, className }),
fixHeight({ autoHeight, size }),
loadingAbsolute && 'relative',
loadingAbsolute && 'relative'
)}
ref={ref}
disabled={loading || disabled}
ref={ref}
type={type}
{...props}
>
{loading && (
<div
className={cn(
loadingAbsolute &&
'absolute top-0 left-0 right-0 bottom-0 center-center backdrop-blur bg-background/10',
'center-center absolute top-0 right-0 bottom-0 left-0 bg-background/10 backdrop-blur'
)}
>
<Spinner
type={loadingType}
size={spinnerSize}
speed={loadingSpeed}
variant={
variant === 'default' || variant === 'cta' ? 'white' : 'default'
}
className={cn(
'flex-shrink-0',
size !== 'icon' && responsive && 'mr-0 sm:mr-2',
size !== 'icon' && !responsive && 'mr-2',
size !== 'icon' && !responsive && 'mr-2'
)}
size={spinnerSize}
speed={loadingSpeed}
type={loadingType}
variant={
variant === 'default' || variant === 'cta' ? 'white' : 'default'
}
/>
</div>
)}
@@ -132,7 +137,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
className={cn(
'h-4 w-4 flex-shrink-0',
size !== 'icon' && responsive && 'mr-0 sm:mr-2',
size !== 'icon' && !responsive && 'mr-2',
size !== 'icon' && !responsive && 'mr-2'
)}
/>
)}
@@ -143,7 +148,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
)}
</Comp>
);
},
}
);
Button.displayName = 'Button';
@@ -180,24 +185,24 @@ const LinkButton = ({
<>
{loading && (
<Spinner
type={loadingType}
size={spinnerSize}
speed={loadingSpeed}
variant={
variant === 'default' || variant === 'cta' ? 'white' : 'default'
}
className={cn(
'flex-shrink-0',
responsive && 'mr-0 sm:mr-2',
!responsive && 'mr-2',
!responsive && 'mr-2'
)}
size={spinnerSize}
speed={loadingSpeed}
type={loadingType}
variant={
variant === 'default' || variant === 'cta' ? 'white' : 'default'
}
/>
)}
{Icon && (
<Icon
className={cn(
'mr-2 h-4 w-4 flex-shrink-0',
responsive && 'mr-0 sm:mr-2',
responsive && 'mr-0 sm:mr-2'
)}
/>
)}

View File

@@ -1,28 +1,7 @@
import type { RouterOutputs } from '@/trpc/client';
import { SheetContent } from '@/components/ui/sheet';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { popModal } from '.';
import { ModalHeader } from './Modal/Container';
import { ColorSquare } from '@/components/color-square';
import { InputWithLabel, WithLabel } from '@/components/forms/input-with-label';
import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { ComboboxEvents } from '@/components/ui/combobox-events';
import { Textarea } from '@/components/ui/textarea';
import { useAppParams } from '@/hooks/use-app-params';
import { useEventNames } from '@/hooks/use-event-names';
import { useEventProperties } from '@/hooks/use-event-properties';
import { useTRPC } from '@/integrations/trpc/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { shortId } from '@openpanel/common';
import { zCreateNotificationRule } from '@openpanel/validation';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { FilterIcon, PlusIcon, SaveIcon, TrashIcon } from 'lucide-react';
import {
Controller,
@@ -32,7 +11,24 @@ import {
useForm,
useWatch,
} from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod';
import { popModal } from '.';
import { ModalHeader } from './Modal/Container';
import { ColorSquare } from '@/components/color-square';
import { InputWithLabel, WithLabel } from '@/components/forms/input-with-label';
import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { ComboboxEvents } from '@/components/ui/combobox-events';
import { SheetContent } from '@/components/ui/sheet';
import { Textarea } from '@/components/ui/textarea';
import { useAppParams } from '@/hooks/use-app-params';
import { useEventNames } from '@/hooks/use-event-names';
import { useEventProperties } from '@/hooks/use-event-properties';
import { useTRPC } from '@/integrations/trpc/react';
import type { RouterOutputs } from '@/trpc/client';
interface Props {
rule?: RouterOutputs['notification']['rules'][number];
@@ -71,21 +67,21 @@ export default function AddNotificationRule({ rule }: Props) {
trpc.notification.createOrUpdateRule.mutationOptions({
onSuccess() {
toast.success(
rule ? 'Notification rule updated' : 'Notification rule created',
rule ? 'Notification rule updated' : 'Notification rule created'
);
client.refetchQueries(
trpc.notification.rules.queryFilter({
projectId,
}),
})
);
popModal();
},
}),
})
);
const integrationsQuery = useQuery(
trpc.integration.list.queryOptions({
organizationId: organizationId!,
}),
})
);
const eventsArray = useFieldArray({
@@ -106,18 +102,18 @@ export default function AddNotificationRule({ rule }: Props) {
return (
<SheetContent className="[&>button.absolute]:hidden">
<ModalHeader title={rule ? 'Edit rule' : 'Create rule'} />
<form onSubmit={form.handleSubmit(onSubmit)} className="col gap-4">
<form className="col gap-4" onSubmit={form.handleSubmit(onSubmit)}>
<InputWithLabel
error={form.formState.errors.name?.message}
label="Rule name"
placeholder="Eg. Sign ups on android"
error={form.formState.errors.name?.message}
{...form.register('name')}
/>
<WithLabel
label="Type"
// @ts-expect-error
error={form.formState.errors.config?.type.message}
label="Type"
>
<Controller
control={form.control}
@@ -126,7 +122,6 @@ export default function AddNotificationRule({ rule }: Props) {
<Combobox
{...field}
className="w-full"
placeholder="Select type"
// @ts-expect-error
error={form.formState.errors.config?.type.message}
items={[
@@ -139,6 +134,7 @@ export default function AddNotificationRule({ rule }: Props) {
value: 'funnel',
},
]}
placeholder="Select type"
/>
)}
/>
@@ -148,16 +144,15 @@ export default function AddNotificationRule({ rule }: Props) {
{eventsArray.fields.map((field, index) => {
return (
<EventField
key={field.id}
form={form}
index={index}
key={field.id}
remove={() => eventsArray.remove(index)}
/>
);
})}
<Button
className="self-start"
variant={'outline'}
icon={PlusIcon}
onClick={() =>
eventsArray.append({
@@ -166,6 +161,7 @@ export default function AddNotificationRule({ rule }: Props) {
segment: 'event',
})
}
variant={'outline'}
>
Add event
</Button>
@@ -173,7 +169,6 @@ export default function AddNotificationRule({ rule }: Props) {
</WithLabel>
<WithLabel
label="Template"
info={
<div className="prose dark:prose-invert">
<p>
@@ -197,7 +192,7 @@ export default function AddNotificationRule({ rule }: Props) {
profile property
</li>
<li>
<div className="flex gap-x-2 flex-wrap">
<div className="flex flex-wrap gap-x-2">
And many more...
<code>profileId</code>
<code>createdAt</code>
@@ -220,6 +215,7 @@ export default function AddNotificationRule({ rule }: Props) {
</ul>
</div>
}
label="Template"
>
<Textarea
{...form.register('template')}
@@ -234,19 +230,19 @@ export default function AddNotificationRule({ rule }: Props) {
<WithLabel label="Integrations">
<ComboboxAdvanced
{...field}
value={field.value ?? []}
className="w-full"
placeholder="Pick integrations"
items={integrations.map((integration) => ({
label: integration.name,
value: integration.id,
}))}
placeholder="Pick integrations"
value={field.value ?? []}
/>
</WithLabel>
)}
/>
<Button type="submit" icon={SaveIcon}>
<Button icon={SaveIcon} type="submit">
{rule ? 'Update' : 'Create'}
</Button>
</form>
@@ -276,27 +272,24 @@ function EventField({
const properties = useEventProperties({ projectId });
return (
<div className="border bg-def-100 rounded">
<div className="row gap-2 items-center p-2">
<div className="rounded border bg-def-100">
<div className="row items-center gap-2 p-2">
<ColorSquare>{index + 1}</ColorSquare>
<Controller
control={form.control}
name={`config.events.${index}.name`}
render={({ field }) => (
<ComboboxEvents
searchable
className="flex-1"
value={field.value}
placeholder="Select event"
onChange={field.onChange}
items={eventNames}
onChange={field.onChange}
placeholder="Select event"
searchable
value={field.value}
/>
)}
/>
<Combobox
searchable
placeholder="Select a filter"
value=""
items={properties.map((item) => ({
label: item,
value: item,
@@ -309,27 +302,33 @@ function EventField({
value: [],
});
}}
placeholder="Select a filter"
searchable
value=""
>
<Button variant={'outline'} icon={FilterIcon} size={'icon'} />
<Button icon={FilterIcon} size={'icon'} variant={'outline'} />
</Combobox>
<Button
className="text-destructive"
icon={TrashIcon}
onClick={() => {
remove();
}}
variant={'outline'}
className="text-destructive"
icon={TrashIcon}
size={'icon'}
variant={'outline'}
/>
</div>
{filtersArray.fields.map((filter, index) => {
return (
<div key={filter.id} className="p-2 border-t">
<div className="border-t p-2" key={filter.id}>
<PureFilterItem
eventName={eventName}
filter={filter}
onRemove={() => {
filtersArray.remove(index);
onChangeOperator={(operator) => {
filtersArray.update(index, {
...filter,
operator,
});
}}
onChangeValue={(value) => {
filtersArray.update(index, {
@@ -337,11 +336,8 @@ function EventField({
value,
});
}}
onChangeOperator={(operator) => {
filtersArray.update(index, {
...filter,
operator,
});
onRemove={() => {
filtersArray.remove(index);
}}
/>
</div>