Skip to content

Commit fd13e7f

Browse files
committed
Merge branch 'next' into nv-5303-migrate-dashboard-v2
2 parents 55f7e0d + 058456d commit fd13e7f

27 files changed

+310
-257
lines changed

apps/api/src/app/shared/framework/idempotency.interceptor.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ export class IdempotencyInterceptor implements NestInterceptor {
5151
return await this.featureFlagService.getFlag({
5252
key: FeatureFlagsKeysEnum.IS_API_IDEMPOTENCY_ENABLED,
5353
defaultValue: false,
54-
environment: { _id: environmentId } as EnvironmentEntity,
55-
organization: { _id: organizationId } as OrganizationEntity,
56-
user: { _id } as UserEntity,
54+
environment: { _id: environmentId },
55+
organization: { _id: organizationId },
56+
user: { _id },
5757
});
5858
}
5959

apps/api/src/app/workflows-v1/usecases/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
UpdateWorkflow,
77
} from '@novu/application-generic';
88

9+
import { CommunityOrganizationRepository } from '@novu/dal';
910
import { GetActiveIntegrationsStatus } from './get-active-integrations-status/get-active-integrations-status.usecase';
1011
import { ChangeTemplateActiveStatus } from './change-template-active-status/change-template-active-status.usecase';
1112
import { GetNotificationTemplates } from './get-notification-templates/get-notification-templates.usecase';
@@ -25,4 +26,5 @@ export const USE_CASES = [
2526
GetNotificationTemplate,
2627
DeleteNotificationTemplate,
2728
GetWorkflowVariables,
29+
CommunityOrganizationRepository,
2830
];

apps/api/src/app/workflows-v2/usecases/build-step-issues/build-step-issues.usecase.ts

+12
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,18 @@ export class BuildStepIssuesUsecase {
320320
}): Promise<StepIssuesDto> {
321321
const issues: StepIssuesDto = {};
322322

323+
const integrationNeeded = [
324+
StepTypeEnum.EMAIL,
325+
StepTypeEnum.SMS,
326+
StepTypeEnum.IN_APP,
327+
StepTypeEnum.PUSH,
328+
StepTypeEnum.CHAT,
329+
].includes(args.stepTypeDto);
330+
331+
if (!integrationNeeded) {
332+
return issues;
333+
}
334+
323335
const primaryNeeded = args.stepTypeDto === StepTypeEnum.EMAIL || args.stepTypeDto === StepTypeEnum.SMS;
324336
const validIntegrationForStep = await this.integrationsRepository.findOne({
325337
_environmentId: args.environmentId,

apps/dashboard/src/components/activity/activity-empty-state.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function ActivityEmptyState({
2121
className,
2222
emptySearchResults,
2323
onClearFilters,
24-
emptySearchTitle = 'No activity match that filter',
24+
emptySearchTitle = 'No activity matches that filter',
2525
emptySearchDescription = 'Try adjusting your filters to see more results.',
2626
emptyFiltersTitle = 'No activity in the past 30 days',
2727
emptyFiltersDescription = 'Your activity feed is empty. Once you trigger your first workflow, you can monitor notifications and view delivery details.',

apps/dashboard/src/components/primitives/avatar.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as React from 'react';
21
import * as AvatarPrimitive from '@radix-ui/react-avatar';
2+
import * as React from 'react';
33

44
import { cn } from '@/utils/ui';
55

@@ -29,10 +29,10 @@ const AvatarFallback = React.forwardRef<
2929
>(({ className, ...props }, ref) => (
3030
<AvatarPrimitive.Fallback
3131
ref={ref}
32-
className={cn('bg-muted flex h-full w-full items-center justify-center rounded-full', className)}
32+
className={cn('bg-bg-weak flex h-full w-full items-center justify-center rounded-full', className)}
3333
{...props}
3434
/>
3535
));
3636
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
3737

38-
export { Avatar, AvatarImage, AvatarFallback };
38+
export { Avatar, AvatarFallback, AvatarImage };

apps/dashboard/src/components/primitives/button.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const buttonVariants = tv({
3737
mode: {
3838
filled: {},
3939
outline: {
40-
root: 'ring-1 ring-inset',
40+
root: 'border',
4141
},
4242
lighter: {
4343
root: 'ring-1 ring-inset',

apps/dashboard/src/components/subscribers/create-subscriber-form.tsx

+78-48
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/primitives/avatar';
2+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/primitives/tooltip';
13
import { useCreateSubscriber } from '@/hooks/use-create-subscriber';
4+
import { useTelemetry } from '@/hooks/use-telemetry';
5+
import { TelemetryEvent } from '@/utils/telemetry';
26
import { zodResolver } from '@hookform/resolvers/zod';
37
import { loadLanguage } from '@uiw/codemirror-extensions-langs';
48
import { useForm } from 'react-hook-form';
@@ -19,8 +23,6 @@ import TruncatedText from '../truncated-text';
1923
import { LocaleSelect } from './locale-select';
2024
import { CreateSubscriberFormSchema } from './schema';
2125
import { TimezoneSelect } from './timezone-select';
22-
import { useTelemetry } from '@/hooks/use-telemetry';
23-
import { TelemetryEvent } from '@/utils/telemetry';
2426

2527
const extensions = [loadLanguage('json')?.extension ?? []];
2628
const basicSetup = { lineNumbers: true, defaultKeymap: true };
@@ -86,6 +88,9 @@ export const CreateSubscriberForm = (props: CreateSubscriberFormProps) => {
8688
});
8789
};
8890

91+
const firstNameChar = form.getValues('firstName')?.charAt(0) || '';
92+
const lastNameChar = form.getValues('lastName')?.charAt(0) || '';
93+
8994
return (
9095
<div className="flex h-full flex-col">
9196
<header className="border-bg-soft flex h-12 w-full flex-row items-center gap-3 border-b p-3.5">
@@ -97,49 +102,74 @@ export const CreateSubscriberForm = (props: CreateSubscriberFormProps) => {
97102
<Form {...form}>
98103
<FormRoot autoComplete="off" noValidate onSubmit={form.handleSubmit(onSubmit)} className="flex h-full flex-col">
99104
<div className="flex flex-col items-stretch gap-6 p-5">
100-
<div className="grid grid-cols-2 gap-2.5">
101-
<FormField
102-
control={form.control}
103-
name="firstName"
104-
render={({ field, fieldState }) => (
105-
<FormItem>
106-
<FormLabel>First Name</FormLabel>
107-
<FormControl>
108-
<Input
109-
{...field}
110-
placeholder={field.name}
111-
id={field.name}
112-
value={field.value}
113-
onChange={field.onChange}
114-
hasError={!!fieldState.error}
115-
size="xs"
116-
/>
117-
</FormControl>
118-
<FormMessage />
119-
</FormItem>
120-
)}
121-
/>
122-
<FormField
123-
control={form.control}
124-
name="lastName"
125-
render={({ field, fieldState }) => (
126-
<FormItem>
127-
<FormLabel>Last Name</FormLabel>
128-
<FormControl>
129-
<Input
130-
{...field}
131-
placeholder={field.name}
132-
id={field.name}
133-
value={field.value}
134-
onChange={field.onChange}
135-
hasError={!!fieldState.error}
136-
size="xs"
137-
/>
138-
</FormControl>
139-
<FormMessage />
140-
</FormItem>
141-
)}
142-
/>
105+
<div className="flex items-center gap-3">
106+
<Tooltip>
107+
<TooltipTrigger
108+
type="button"
109+
onClick={(e) => {
110+
e.preventDefault();
111+
e.stopPropagation();
112+
}}
113+
>
114+
<Avatar className="size-[3.75rem] cursor-default">
115+
<AvatarImage src={firstNameChar || lastNameChar ? '' : '/images/avatar.svg'} />
116+
<AvatarFallback>
117+
{firstNameChar || lastNameChar ? (
118+
firstNameChar + lastNameChar
119+
) : (
120+
<AvatarImage src="/images/avatar.svg" />
121+
)}
122+
</AvatarFallback>
123+
</Avatar>
124+
</TooltipTrigger>
125+
<TooltipContent className="max-w-56">
126+
Subscriber profile Image can only be updated via API
127+
</TooltipContent>
128+
</Tooltip>
129+
<div className="grid w-full grid-cols-2 gap-2.5">
130+
<FormField
131+
control={form.control}
132+
name="firstName"
133+
render={({ field, fieldState }) => (
134+
<FormItem>
135+
<FormLabel>First Name</FormLabel>
136+
<FormControl>
137+
<Input
138+
{...field}
139+
placeholder={'John'}
140+
id={field.name}
141+
value={field.value}
142+
onChange={field.onChange}
143+
hasError={!!fieldState.error}
144+
size="xs"
145+
/>
146+
</FormControl>
147+
<FormMessage />
148+
</FormItem>
149+
)}
150+
/>
151+
<FormField
152+
control={form.control}
153+
name="lastName"
154+
render={({ field, fieldState }) => (
155+
<FormItem>
156+
<FormLabel>Last Name</FormLabel>
157+
<FormControl>
158+
<Input
159+
{...field}
160+
placeholder={'Doe'}
161+
id={field.name}
162+
value={field.value}
163+
onChange={field.onChange}
164+
hasError={!!fieldState.error}
165+
size="xs"
166+
/>
167+
</FormControl>
168+
<FormMessage />
169+
</FormItem>
170+
)}
171+
/>
172+
</div>
143173
</div>
144174
<div>
145175
<FormField
@@ -238,12 +268,12 @@ export const CreateSubscriberForm = (props: CreateSubscriberFormProps) => {
238268
/>
239269
</div>
240270

241-
<div className="flex flex-nowrap gap-2.5">
271+
<div className="grid grid-cols-[1fr_3fr] gap-2.5">
242272
<FormField
243273
control={form.control}
244274
name="locale"
245275
render={({ field }) => (
246-
<FormItem className="w-1/4">
276+
<FormItem>
247277
<FormLabel>Locale</FormLabel>
248278
<FormControl>
249279
<LocaleSelect
@@ -262,7 +292,7 @@ export const CreateSubscriberForm = (props: CreateSubscriberFormProps) => {
262292
control={form.control}
263293
name="timezone"
264294
render={({ field }) => (
265-
<FormItem className="flex-1">
295+
<FormItem className="overflow-hidden">
266296
<FormLabel>Timezone</FormLabel>
267297
<FormControl>
268298
<TimezoneSelect

apps/dashboard/src/components/subscribers/locale-select.tsx

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import { locales } from '@/utils/locales';
22
import { cn } from '@/utils/ui';
3+
import { useRef, useState } from 'react';
34
import { RiArrowDownSLine, RiCheckLine, RiEarthLine, RiSearchLine } from 'react-icons/ri';
45
import { type Country } from 'react-phone-number-input';
56
import flags from 'react-phone-number-input/flags';
6-
import { Button } from '../primitives/button';
7+
import { Button, ButtonProps } from '../primitives/button';
78
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../primitives/command';
89
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover';
910
import TruncatedText from '../truncated-text';
10-
import { useRef, useState } from 'react';
1111

12-
export function LocaleSelect({
13-
value,
14-
onChange,
15-
disabled,
16-
readOnly,
17-
}: {
12+
type LocaleSelectProps = ButtonProps & {
1813
value?: string;
1914
disabled?: boolean;
2015
readOnly?: boolean;
2116
onChange: (val: string) => void;
22-
}) {
17+
};
18+
19+
export function LocaleSelect(props: LocaleSelectProps) {
20+
const { value, disabled, readOnly, onChange, className, ...rest } = props;
2321
const [open, setOpen] = useState(false);
2422
const currentCountryCode = value?.split('_')?.[1] as Country;
2523
const CurrentFlag = currentCountryCode ? flags[currentCountryCode] : RiEarthLine;
@@ -33,8 +31,9 @@ export function LocaleSelect({
3331
<Button
3432
variant="secondary"
3533
mode="outline"
36-
className="flex h-8 w-full items-center justify-between gap-1 rounded-lg border-r-0 px-3 focus:z-10"
34+
className={cn('flex h-8 w-full items-center justify-between gap-1 rounded-lg px-3 focus:z-10', className)}
3735
disabled={disabled}
36+
{...rest}
3837
>
3938
<div className="flex max-w-full flex-1 items-center gap-1 overflow-hidden">
4039
<span className="inline-flex gap-1 text-xs font-normal text-neutral-950">

apps/dashboard/src/components/subscribers/preferences/preferences-item.tsx

+2-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Switch } from '@/components/primitives/switch';
44
import { STEP_TYPE_TO_COLOR } from '@/utils/color';
55
import { capitalize } from '@/utils/string';
66
import { ChannelTypeEnum } from '@novu/shared';
7-
import { motion } from 'motion/react';
87

98
const CHANNEL_LABELS_LOOKUP: Record<`${ChannelTypeEnum}`, string> = {
109
[ChannelTypeEnum.IN_APP]: 'In-App',
@@ -26,13 +25,7 @@ export function PreferencesItem(props: PreferencesItemProps) {
2625
const Icon = STEP_TYPE_TO_ICON[channel];
2726

2827
return (
29-
<motion.div
30-
variants={{
31-
hidden: { opacity: 0, y: 20 },
32-
visible: { opacity: 1, y: 0 },
33-
}}
34-
transition={{ duration: 0.3 }}
35-
>
28+
<div>
3629
<div className="mt-2 flex w-full items-center justify-between space-y-1">
3730
<div className="flex items-center gap-2">
3831
<Step variant={STEP_TYPE_TO_COLOR[channel]} className="size-5">
@@ -42,6 +35,6 @@ export function PreferencesItem(props: PreferencesItemProps) {
4235
</div>
4336
<Switch checked={enabled} onCheckedChange={readOnly ? undefined : onChange} />
4437
</div>
45-
</motion.div>
38+
</div>
4639
);
4740
}

0 commit comments

Comments
 (0)