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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface PathCardProps {
iconTone?: 'default' | 'warning';
title: string;
description: string;
meta: ReactNode;
meta?: ReactNode;
disabled?: boolean;
}

Expand Down Expand Up @@ -57,9 +57,7 @@ export function BackgroundCheckPathCard({
className={cn(
'group relative rounded-[var(--radius)] border px-4 py-3.5 transition-colors duration-200 ease-out',
'outline-none focus-visible:ring-[3px] focus-visible:ring-primary/20',
disabled
? 'cursor-not-allowed opacity-60'
: 'cursor-pointer hover:border-muted-foreground',
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:border-muted-foreground',
selected
? 'border-primary bg-[oklch(0.985_0.012_167)] shadow-[inset_0_0_0_1px_var(--primary)]'
: 'border-border bg-background',
Expand All @@ -81,7 +79,7 @@ export function BackgroundCheckPathCard({
</div>
<div className="mb-[3px] text-sm font-normal text-foreground">{title}</div>
<div className="mb-2 text-[13px] leading-[1.4] text-muted-foreground">{description}</div>
<div className="font-mono text-xs">{meta}</div>
{meta ? <div className="font-mono text-xs">{meta}</div> : null}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
'use client';

import { Text } from '@trycompai/design-system';
import { Attachment, Flash, Security, Warning } from '@trycompai/design-system/icons';
import { Attachment, Flash, Security } from '@trycompai/design-system/icons';
import { useState } from 'react';
import {
BackgroundCheckAttachForm,
type AttachFormValues,
} from './BackgroundCheckAttachForm';
import {
BackgroundCheckExemptForm,
type ExemptFormValues,
} from './BackgroundCheckExemptForm';
import {
BackgroundCheckOrderForm,
type OrderFormValues,
} from './BackgroundCheckOrderForm';
import { BackgroundCheckAttachForm, type AttachFormValues } from './BackgroundCheckAttachForm';
import { BackgroundCheckExemptForm, type ExemptFormValues } from './BackgroundCheckExemptForm';
import { BackgroundCheckOrderForm, type OrderFormValues } from './BackgroundCheckOrderForm';
import { BackgroundCheckPathCard } from './BackgroundCheckPathCard';
import { BackgroundCheckScopePanel } from './BackgroundCheckScopePanel';
import { BackgroundCheckStatusStrip } from './BackgroundCheckStatusStrip';
Expand Down Expand Up @@ -90,7 +81,11 @@ export function BackgroundCheckV1Page(props: V1PageProps) {
How would you like to proceed?
</div>

<div role="radiogroup" aria-label="How would you like to proceed?" className="mb-6 grid gap-3 md:grid-cols-3">
<div
role="radiogroup"
aria-label="How would you like to proceed?"
className="mb-6 grid gap-3 md:grid-cols-3"
>
<BackgroundCheckPathCard
selected={selectedPath === 'order'}
onSelect={() => handleSelect('order')}
Expand All @@ -116,12 +111,6 @@ export function BackgroundCheckV1Page(props: V1PageProps) {
icon={Security}
title="Mark as exempt"
description="This employee won't be required to pass a check."
meta={
<span className="inline-flex items-center gap-1 text-[var(--warning)]">
<Warning size={12} />
Logs a compliance exception
</span>
}
/>
</div>

Expand All @@ -141,7 +130,9 @@ export function BackgroundCheckV1Page(props: V1PageProps) {
Boolean(props.orderValues.employeeEmail)
}
disabledReason={
!props.hasAllowance ? "You're out of credits. Choose a plan to continue." : undefined
!props.hasAllowance
? "You're out of credits. Choose a plan to continue."
: undefined
}
/>
)}
Expand All @@ -152,7 +143,9 @@ export function BackgroundCheckV1Page(props: V1PageProps) {
onSubmit={props.onAttachSubmit}
submitting={props.isAttachSubmitting}
canSubmit={
props.canRequest && Boolean(props.attachValues.vendor) && Boolean(props.attachValues.file)
props.canRequest &&
Boolean(props.attachValues.vendor) &&
Boolean(props.attachValues.file)
}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('EmployeeBackgroundCheck — V1 two-paths', () => {
expect(screen.getByText('Order a new check')).toBeInTheDocument();
expect(screen.getByText('Attach an existing report')).toBeInTheDocument();
expect(screen.getByText('Mark as exempt')).toBeInTheDocument();
expect(screen.queryByText('Logs a compliance exception')).not.toBeInTheDocument();

const orderCard = screen.getByRole('radio', { name: /Order a new check/i });
expect(orderCard).toHaveAttribute('aria-checked', 'true');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,36 @@ describe('BrandSettings permission gating', () => {
expect(handlePrimaryColorChange).toHaveBeenCalledWith('#00FF00');
expect(navigationMock.refresh).toHaveBeenCalled();
});

it('persists an empty brand color as a reset to default branding', async () => {
setMockPermissions(ADMIN_PERMISSIONS);
const handlePrimaryColorChange = vi.fn();

render(<BrandSettings {...defaultProps} onPrimaryColorChange={handlePrimaryColorChange} />);

const textInput = screen.getByRole('textbox');
fireEvent.change(textInput, { target: { value: '' } });

await waitFor(() => {
expect(trustPortalSettingsMock.updateToggleSettings).toHaveBeenCalledWith({
enabled: true,
primaryColor: '',
});
});
expect(handlePrimaryColorChange).toHaveBeenCalledWith(null);
expect(navigationMock.refresh).toHaveBeenCalled();
});

it('does not persist an invalid brand color', () => {
setMockPermissions(ADMIN_PERMISSIONS);

render(<BrandSettings {...defaultProps} />);

const textInput = screen.getByRole('textbox');
fireEvent.change(textInput, { target: { value: 'zzzzzz' } });
fireEvent.blur(textInput);

expect(trustPortalSettingsMock.updateToggleSettings).not.toHaveBeenCalled();
expect(navigationMock.refresh).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ interface BrandSettingsProps {
onPrimaryColorChange?: (primaryColor: string | null) => void;
}

function normalizePrimaryColor(value: unknown): string | null {
if (typeof value !== 'string' || value.length === 0) return null;
if (!HEX_COLOR_PATTERN.test(value)) return null;
return value.toUpperCase();
function normalizePrimaryColor(value: unknown): string | null | undefined {
if (value === null) return null;
if (typeof value !== 'string') return undefined;

const trimmedValue = value.trim();
if (trimmedValue.length === 0) return null;
if (!HEX_COLOR_PATTERN.test(trimmedValue)) return undefined;

return trimmedValue.toUpperCase();
}

export function BrandSettings({
Expand All @@ -57,7 +62,7 @@ export function BrandSettings({
});

const lastSaved = useRef<{ [key: string]: string | null }>({
primaryColor: normalizePrimaryColor(primaryColor),
primaryColor: normalizePrimaryColor(primaryColor) ?? null,
});

const savingRef = useRef<{ [key: string]: boolean }>({
Expand All @@ -71,7 +76,7 @@ export function BrandSettings({
}

const nextPrimaryColor = normalizePrimaryColor(value);
if (!nextPrimaryColor) {
if (nextPrimaryColor === undefined) {
return;
}

Expand All @@ -82,7 +87,7 @@ export function BrandSettings({
enabled,
primaryColor:
field === 'primaryColor'
? nextPrimaryColor
? (nextPrimaryColor ?? '')
: (form.getValues('primaryColor') ?? undefined),
});
toast.success('Brand settings updated');
Expand All @@ -106,7 +111,7 @@ export function BrandSettings({
const normalizedPrimaryColor = normalizePrimaryColor(primaryColor);
form.reset({ primaryColor: normalizedPrimaryColor ?? undefined });
setPrimaryColorValue(normalizedPrimaryColor ?? '');
lastSaved.current.primaryColor = normalizedPrimaryColor;
lastSaved.current.primaryColor = normalizedPrimaryColor ?? null;
}, [form, primaryColor]);

useEffect(() => {
Expand All @@ -116,8 +121,8 @@ export function BrandSettings({
!savingRef.current.primaryColor
) {
const normalizedPrimaryColor = normalizePrimaryColor(debouncedPrimaryColor);
if (normalizedPrimaryColor) {
form.setValue('primaryColor', normalizedPrimaryColor);
if (normalizedPrimaryColor !== undefined) {
form.setValue('primaryColor', normalizedPrimaryColor ?? undefined);
void autoSave('primaryColor', normalizedPrimaryColor);
}
}
Expand All @@ -126,12 +131,12 @@ export function BrandSettings({
const handlePrimaryColorBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
const value = normalizePrimaryColor(e.target.value);
if (!value) {
if (value === undefined) {
toast.error('Enter a valid hex color');
return;
}
form.setValue('primaryColor', value);
setPrimaryColorValue(value);
form.setValue('primaryColor', value ?? undefined);
setPrimaryColorValue(value ?? '');
void autoSave('primaryColor', value);
},
[form, autoSave],
Expand Down Expand Up @@ -181,7 +186,7 @@ export function BrandSettings({
<div className="flex-1">
<div className="font-mono">
<Input
value={primaryColorValue?.toUpperCase() || DEFAULT_PRIMARY_COLOR}
value={primaryColorValue?.toUpperCase() ?? ''}
onChange={(e) => {
let value = e.target.value;
if (value.length > 0 && !value.startsWith('#')) {
Expand Down
Loading