Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
903 changes: 0 additions & 903 deletions .cspell.json

This file was deleted.

13 changes: 0 additions & 13 deletions .github/workflows/on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,6 @@ jobs:
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4

spellcheck:
name: Spell check
runs-on: blacksmith-4vcpu-ubuntu-2404
environment: Linting
steps:
- uses: actions/checkout@v5
- name: Run Spell Check
uses: streetsidesoftware/cspell-action@v6
with:
root: 'apps/dashboard'
files: '**/*'
incremental_files_only: true

find-flags:
runs-on: blacksmith-4vcpu-ubuntu-2404
name: Find LaunchDarkly feature flags in diff
Expand Down
2 changes: 1 addition & 1 deletion .source
10 changes: 5 additions & 5 deletions apps/api/src/app/subscribers-v2/subscribers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,11 @@ export class SubscribersController {
): Promise<GetPreferencesResponseDto[]> {
const preferences = body.preferences.map((preference) => ({
workflowId: preference.workflowId,
email: preference.channels.email,
sms: preference.channels.sms,
in_app: preference.channels.in_app,
push: preference.channels.push,
chat: preference.channels.chat,
email: preference.channels?.email,
sms: preference.channels?.sms,
in_app: preference.channels?.in_app,
push: preference.channels?.push,
chat: preference.channels?.chat,
}));

return await this.bulkUpdatePreferencesUsecase.execute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,24 @@ export class CreateSubscriptionsUsecase {
if (!topic) {
this.validateTopicKey(command.topicKey);

topic = await this.topicRepository.createTopic({
_environmentId: command.environmentId,
_organizationId: command.organizationId,
key: command.topicKey,
name: command.name,
});
try {
topic = await this.topicRepository.createTopic({
_environmentId: command.environmentId,
_organizationId: command.organizationId,
key: command.topicKey,
name: command.name,
});
} catch (error: unknown) {
if (this.isDuplicateKeyError(error)) {
topic = await this.topicRepository.findTopicByKey(
command.topicKey,
command.organizationId,
command.environmentId
);
} else {
throw error;
}
}
} else if (command.name) {
topic = await this.topicRepository.findOneAndUpdate(
{
Expand Down Expand Up @@ -338,6 +350,10 @@ export class CreateSubscriptionsUsecase {
);
}

private isDuplicateKeyError(error: unknown): boolean {
return typeof error === 'object' && error !== null && 'code' in error && (error as { code: number }).code === 11000;
}

private async validateSubscriptionLimit(
topic: TopicEntity,
subscribers: SubscriberEntity[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { TopicResponseDto } from '../../dtos/topic-response.dto';
import { mapTopicEntityToDto } from '../list-topics/map-topic-entity-to.dto';
import { UpsertTopicCommand } from './upsert-topic.command';

const DUPLICATE_KEY_ERROR_CODE = 11000;

@Injectable()
export class UpsertTopicUseCase {
constructor(private topicRepository: TopicRepository) {}
Expand All @@ -20,12 +22,24 @@ export class UpsertTopicUseCase {
if (!topic) {
this.isValidTopicKey(command.key);

topic = await this.topicRepository.createTopic({
_environmentId: command.environmentId,
_organizationId: command.organizationId,
key: command.key,
name: command.name,
});
try {
topic = await this.topicRepository.createTopic({
_environmentId: command.environmentId,
_organizationId: command.organizationId,
key: command.key,
name: command.name,
});
} catch (error: unknown) {
if (this.isDuplicateKeyError(error)) {
topic = await this.topicRepository.findTopicByKey(
command.key,
command.organizationId,
command.environmentId
);
} else {
throw error;
}
}
} else {
const updateBody: Record<string, unknown> = {};

Expand Down Expand Up @@ -60,4 +74,8 @@ export class UpsertTopicUseCase {
`Invalid topic key: "${key}". Topic keys must contain only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), underscores (_), colons (:), or be a valid email address.`
);
}

private isDuplicateKeyError(error: unknown): boolean {
return typeof error === 'object' && error !== null && 'code' in error && error.code === DUPLICATE_KEY_ERROR_CODE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export class GetActiveIntegrationsStatus {
stepType === StepTypeEnum.DELAY ||
stepType === StepTypeEnum.DIGEST ||
stepType === StepTypeEnum.TRIGGER ||
stepType === StepTypeEnum.CUSTOM;
stepType === StepTypeEnum.CUSTOM ||
!activeChannelsStatus[stepType];
const isStepWithPrimaryIntegration = stepType === StepTypeEnum.EMAIL || stepType === StepTypeEnum.SMS;
if (stepType && !skipStep) {
const { hasActiveIntegrations } = activeChannelsStatus[stepType];
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/auth-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Toaster } from './primitives/sonner';

export const AuthLayout = ({ children }: { children: ReactNode }) => {
return (
<div className="flex min-h-screen items-center justify-center bg-[url('/images/auth/background.svg')] bg-cover bg-no-repeat">
<div className="flex min-h-screen items-center justify-center overflow-auto bg-[url('/images/auth/background.svg')] bg-cover bg-no-repeat p-4 md:p-0">
<Toaster />

<div className="flex w-full flex-1 flex-row items-center justify-center">{children}</div>
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/components/auth/auth-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ import { cn } from '../../utils/ui';
import { Card } from '../primitives/card';

export function AuthCard({ children, className }: { children: React.ReactNode; className?: string }) {
return <Card className={cn('flex min-h-[692px] w-full max-w-[1100px] overflow-hidden', className)}>{children}</Card>;
return (
<Card className={cn('flex min-h-0 w-full max-w-[1100px] overflow-hidden md:min-h-[692px]', className)}>
{children}
</Card>
);
}
8 changes: 4 additions & 4 deletions apps/dashboard/src/components/auth/create-organization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ interface IllustrationProps {
// Small Components
function FormContainer({ children }: FormContainerProps) {
return (
<div className="flex min-w-[564px] max-w-[564px] items-center p-[60px]">
<div className="flex flex-col gap-[4px]">{children}</div>
<div className="flex w-full items-center p-6 md:min-w-[564px] md:max-w-[564px] md:p-[60px]">
<div className="flex w-full flex-col gap-[4px]">{children}</div>
</div>
);
}
Expand Down Expand Up @@ -118,15 +118,15 @@ function Illustration({ src, alt, className }: IllustrationProps) {

function IllustrationSection() {
return (
<div className="flex flex-1 items-center justify-center">
<div className="hidden flex-1 items-center justify-center md:flex">
<Illustration {...ILLUSTRATION_CONFIG} />
</div>
);
}

function MainContent() {
return (
<div className="flex flex-1">
<div className="flex flex-1 flex-col md:flex-row">
<OrganizationFormSection />
<IllustrationSection />
</div>
Expand Down
12 changes: 5 additions & 7 deletions apps/dashboard/src/components/auth/inbox-playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,18 @@ export function InboxPlayground({ appId, subscriberId }: { appId: string; subscr
backgroundRepeat: 'no-repeat',
}}
>
<div className="flex flex-1">
{/* App Name Section - Show immediately */}
<div className="flex flex-1 items-start justify-start">
<div className="flex flex-1 flex-col md:flex-row">
<div className="hidden flex-1 items-start justify-start md:flex">
<div className="ml-10 mt-9">
<div className="text-1xl font-medium text-gray-500">
{organization?.name ? `${organization.name} App` : 'ACME App'}
</div>
</div>
</div>

{/* Inbox Preview Section - Show with optimized loading */}
<div className="flex flex-1 flex-col">
<div className="flex items-start justify-end">
<div className="nv-no-scrollbar mr-20 mt-16 h-[470px] w-[375px] rounded-lg border border-gray-200 bg-white shadow-[0_8px_25px_-8px_rgba(0,0,0,0.15)]">
<div className="flex flex-1 flex-col items-center md:items-end">
<div className="flex items-start justify-center px-4 py-6 md:justify-end md:px-0">
<div className="nv-no-scrollbar h-[380px] w-full max-w-[375px] overflow-hidden rounded-lg border border-gray-200 bg-white shadow-[0_8px_25px_-8px_rgba(0,0,0,0.15)] md:mr-20 md:mt-16 md:h-[470px] md:w-[375px]">
<InboxPreviewContent />
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/components/auth/inbox-preview-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function InboxPreviewContent() {
backgroundColor: 'white',
},
inboxContent: {
maxHeight: '460px',
maxHeight: '100%',
},
notificationListContainer: {
minHeight: '100%',
Expand All @@ -69,7 +69,7 @@ export function InboxPreviewContent() {
};

return (
<div className="hide-inbox-footer nv-no-scrollbar mt-1 h-[470px] w-[370px] overflow-y-auto overflow-x-hidden">
<div className="hide-inbox-footer nv-no-scrollbar mt-1 h-full w-full overflow-y-auto overflow-x-hidden">
<Inbox {...configuration}>
<InboxContent />
</Inbox>
Expand Down
8 changes: 4 additions & 4 deletions apps/dashboard/src/components/auth/questionnaire-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ export function QuestionnaireForm() {

return (
<>
<div className="w-full max-w-[564px] px-0 pt-[80px]">
<div className="w-full max-w-[564px] px-4 pt-10 md:px-0 md:pt-[80px]">
<div className="flex flex-col items-center gap-8">
<div className="flex w-[350px] flex-col gap-1">
<div className="flex w-full max-w-[350px] flex-col gap-1">
<div className="flex w-full items-center gap-1.5">
<div className="flex flex-1 flex-col gap-1">
<StepIndicator step={2} />
Expand All @@ -100,7 +100,7 @@ export function QuestionnaireForm() {
</div>

<Form {...form}>
<FormRoot onSubmit={handleSubmit(onSubmit)} className="flex w-[350px] flex-col gap-8">
<FormRoot onSubmit={handleSubmit(onSubmit)} className="flex w-full max-w-[350px] flex-col gap-8">
<div className="flex flex-col gap-7">
<div className="flex flex-col gap-[4px]">
<label className="text-foreground-600 text-xs font-medium">Job title</label>
Expand Down Expand Up @@ -227,7 +227,7 @@ export function QuestionnaireForm() {
</div>
</div>

<div className="w-full max-w-[564px] flex-1">
<div className="hidden w-full max-w-[564px] flex-1 md:block">
<img src="/images/auth/ui-org.svg" alt="create-org-illustration" />
</div>
</>
Expand Down
30 changes: 19 additions & 11 deletions apps/dashboard/src/components/contexts/context-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,41 @@ import { formatDateSimple } from '@/utils/format-date';
import { Protect } from '@/utils/protect';
import { buildRoute, ROUTES } from '@/utils/routes';
import { cn } from '@/utils/ui';
import { useContextsNavigate } from './hooks/use-contexts-navigate';

type ContextRowProps = {
context: GetContextResponseDto;
};

type ContextTableCellProps = ComponentProps<typeof TableCell>;
type ContextTableCellProps = ComponentProps<typeof TableCell> & {
to?: string;
};

const ContextTableCell = (props: ContextTableCellProps) => {
const { children, className, ...rest } = props;
const { children, className, to, ...rest } = props;

return (
<TableCell className={cn('group-hover:bg-neutral-alpha-50 text-text-sub relative', className)} {...rest}>
{to && (
<Link to={to} className="absolute inset-0" tabIndex={-1}>
<span className="sr-only">Edit context</span>
</Link>
)}
{children}
</TableCell>
);
};

export const ContextRow = ({ context }: ContextRowProps) => {
const { navigateToEditContextPage } = useContextsNavigate();
const { currentEnvironment } = useEnvironment();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { deleteContext, isPending: isDeleting } = useDeleteContext();

const contextLink = buildRoute(ROUTES.CONTEXTS_EDIT, {
environmentSlug: currentEnvironment?.slug ?? '',
type: context.type,
id: context.id,
});

const stopPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
};
Expand All @@ -66,14 +77,11 @@ export const ContextRow = ({ context }: ContextRowProps) => {
<>
<TableRow
className="group relative isolate cursor-pointer"
onClick={() => {
navigateToEditContextPage(context.type, context.id);
}}
>
<ContextTableCell>
<ContextTableCell to={contextLink}>
<span className="max-w-[300px] truncate font-medium">{context.type}</span>
</ContextTableCell>
<ContextTableCell>
<ContextTableCell to={contextLink}>
<div className="flex items-center gap-1">
<div className="font-code text-text-soft max-w-[300px] truncate">{context.id}</div>
<CopyButton
Expand All @@ -83,12 +91,12 @@ export const ContextRow = ({ context }: ContextRowProps) => {
/>
</div>
</ContextTableCell>
<ContextTableCell>
<ContextTableCell to={contextLink}>
{context.createdAt && (
<TimeDisplayHoverCard date={context.createdAt}>{formatDateSimple(context.createdAt)}</TimeDisplayHoverCard>
)}
</ContextTableCell>
<ContextTableCell>
<ContextTableCell to={contextLink}>
{context.updatedAt && (
<TimeDisplayHoverCard date={context.updatedAt}>{formatDateSimple(context.updatedAt)}</TimeDisplayHoverCard>
)}
Expand Down
14 changes: 12 additions & 2 deletions apps/dashboard/src/components/dashboard-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import { HeaderNavigation } from '@/components/header-navigation/header-navigation';
import { MobileDesktopPrompt } from '@/components/mobile-desktop-prompt';
// @ts-ignore
import { SideNavigation } from '@/components/side-navigation/side-navigation';

Expand All @@ -16,12 +17,21 @@ export const DashboardLayout = ({
}) => {
return (
<div className="relative flex h-full w-full">
{showSideNavigation && <SideNavigation />}
{showSideNavigation && (
<div className="hidden md:block">
<SideNavigation />
</div>
)}
<div className="flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
<HeaderNavigation startItems={headerStartItems} hideBridgeUrl={!showBridgeUrl} />
<HeaderNavigation
startItems={headerStartItems}
hideBridgeUrl={!showBridgeUrl}
showMobileNav={showSideNavigation}
/>

<div className="flex flex-1 flex-col overflow-y-auto overflow-x-hidden p-2">{children}</div>
</div>
<MobileDesktopPrompt />
</div>
);
};
2 changes: 2 additions & 0 deletions apps/dashboard/src/components/full-page-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import { HeaderNavigation } from '@/components/header-navigation/header-navigation';
import { MobileDesktopPrompt } from '@/components/mobile-desktop-prompt';

export const FullPageLayout = ({
children,
Expand All @@ -15,6 +16,7 @@ export const FullPageLayout = ({

<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">{children}</div>
</div>
<MobileDesktopPrompt />
</div>
);
};
Loading
Loading