Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ pnpm --filter webapp build # Build webapp
pnpm --filter extension build:chrome # Build Chrome extension
```

**IMPORTANT**: Do NOT run `build` commands while the dev server is running - it will break hot reload. Only run builds at the end to verify your work compiles successfully. During development, rely on the dev server's hot reload and TypeScript/ESLint checks instead.

## Where Should I Put This Code?

```
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/components/RecruiterLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const RecruiterLayout = ({
<RecruiterLayoutHeader canGoBack={canGoBack} onLogoClick={onLogoClick} />
<NoSidebarLayout
hideBackButton
className="flex flex-col gap-5 py-5 laptop:gap-10 laptop:py-10"
className="flex flex-col gap-5 laptop:gap-10"
>
{children}
</NoSidebarLayout>
Expand Down
15 changes: 13 additions & 2 deletions packages/shared/src/components/accordion/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface AccordionProps {
title: ReactNode;
children: ReactNode;
initiallyOpen?: boolean;
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
onClick?: MouseEventHandler<HTMLButtonElement>;
className?: {
button?: string;
Expand Down Expand Up @@ -69,13 +71,18 @@ export function Accordion({
children,
onClick,
initiallyOpen = false,
isOpen: controlledIsOpen,
onOpenChange,
className,
disabled = false,
}: AccordionProps): ReactElement {
const [isOpen, setIsOpen] = useState(initiallyOpen);
const [internalIsOpen, setInternalIsOpen] = useState(initiallyOpen);
const id = useId();
const contentId = `accordion-content-${id}`;

// Use controlled state if provided, otherwise use internal state
const isControlled = controlledIsOpen !== undefined;
const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
const isOpenAndEnabled = isOpen && !disabled;

const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
Expand All @@ -85,7 +92,11 @@ export function Accordion({

onClick?.(e);

setIsOpen((prev) => !prev);
const newIsOpen = !isOpen;
if (!isControlled) {
setInternalIsOpen(newIsOpen);
}
onOpenChange?.(newIsOpen);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ItalicIcon,
BulletListIcon,
NumberedListIcon,
UndoIcon,
RedoIcon,
} from '../../icons';
import { LinkIcon } from '../../icons/Link';
import { Tooltip } from '../../tooltip/Tooltip';
Expand All @@ -32,25 +34,33 @@ interface ToolbarButtonProps {
icon: ReactElement;
isActive: boolean;
onClick: () => void;
disabled?: boolean;
}

const ToolbarButton = ({
tooltip,
icon,
isActive,
onClick,
}: ToolbarButtonProps): ReactElement => (
<Tooltip content={tooltip}>
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={icon}
pressed={isActive}
onClick={onClick}
type="button"
/>
</Tooltip>
);
disabled = false,
}: ToolbarButtonProps): ReactElement | null => {
if (disabled) {
return null;
}

return (
<Tooltip content={tooltip}>
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={icon}
pressed={isActive}
onClick={onClick}
type="button"
/>
</Tooltip>
);
};

function RichTextToolbarComponent(
{ editor, onLinkAdd }: RichTextToolbarProps,
Expand Down Expand Up @@ -142,6 +152,23 @@ function RichTextToolbarComponent(
isActive={editor.isActive('link')}
onClick={openLinkModal}
/>
{(editor.can().undo() || editor.can().redo()) && (
<div className="mx-1 h-4 w-px bg-border-subtlest-tertiary" />
)}
<ToolbarButton
tooltip="Undo (⌘Z)"
icon={<UndoIcon />}
isActive={false}
onClick={() => editor.chain().focus().undo().run()}
disabled={!editor.can().undo()}
/>
<ToolbarButton
tooltip="Redo (⌘⇧Z)"
icon={<RedoIcon />}
isActive={false}
onClick={() => editor.chain().focus().redo().run()}
disabled={!editor.can().redo()}
/>
</div>
<LinkModal
isOpen={isLinkModalOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface RichTextEditorProps {
placeholder?: string;
maxLength?: number;
onValueUpdate?: (html: string) => void;
onFocus?: () => void;
className?: {
container?: string;
editor?: string;
Expand All @@ -41,6 +42,7 @@ function RichTextEditorComponent(
placeholder = 'Start typing...',
maxLength = 2000,
onValueUpdate,
onFocus,
className = {},
}: RichTextEditorProps,
ref: MutableRefObject<RichTextRef>,
Expand Down Expand Up @@ -92,6 +94,9 @@ function RichTextEditorComponent(
onUpdate: ({ editor: updatedEditor }) => {
onValueUpdate?.(updatedEditor.getHTML());
},
onFocus: () => {
onFocus?.();
},
// Disable immediate render to avoid SSR hydration mismatches
immediatelyRender: false,
});
Expand Down
9 changes: 9 additions & 0 deletions packages/shared/src/components/icons/Redo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactElement } from 'react';
import React from 'react';
import type { IconProps } from '../../Icon';
import Icon from '../../Icon';
import OutlinedIcon from './outlined.svg';

export const RedoIcon = (props: IconProps): ReactElement => (
<Icon {...props} IconPrimary={OutlinedIcon} IconSecondary={OutlinedIcon} />
);
7 changes: 7 additions & 0 deletions packages/shared/src/components/icons/Redo/outlined.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions packages/shared/src/components/icons/Undo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactElement } from 'react';
import React from 'react';
import type { IconProps } from '../../Icon';
import Icon from '../../Icon';
import OutlinedIcon from './outlined.svg';

export const UndoIcon = (props: IconProps): ReactElement => (
<Icon {...props} IconPrimary={OutlinedIcon} IconSecondary={OutlinedIcon} />
);
7 changes: 7 additions & 0 deletions packages/shared/src/components/icons/Undo/outlined.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/shared/src/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export * from './PWA';
export * from './ReadingStreak';
export * from './Reddit';
export * from './Redis';
export * from './Redo';
export * from './Refresh';
export * from './RemoveUser';
export * from './Reputation';
Expand Down Expand Up @@ -159,6 +160,7 @@ export * from './Tour';
export * from './Trash';
export * from './Trending';
export * from './Twitter';
export * from './Undo';
export * from './Unread';
export * from './Upload';
export * from './Upvote';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const RecruiterLayoutHeader = ({
>
{canGoBack && <GoBackButton />}
<div className="mr-auto flex items-center gap-2">
<HeaderLogo onLogoClick={onLogoClick} isRecruiter />
<HeaderLogo onLogoClick={onLogoClick} isRecruiter href="/recruiter" />
</div>
<div className={recruiterLayoutHeaderClassName} />
</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ export const OpportunityReimportModal = ({

const { mutate: reimportOpportunity, isPending } = useMutation({
...reimportOpportunityMutationOptions(),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getOpportunityByIdKey(opportunityId),
});
onSuccess: (data) => {
// Immediately update the cache with the returned data
queryClient.setQueryData(getOpportunityByIdKey(opportunityId), data);
onRequestClose?.(null);
},
onError: (err) => {
Expand Down Expand Up @@ -131,7 +130,7 @@ export const OpportunityReimportModal = ({

<div className="flex w-full flex-col gap-4">
<TextField
label="Job description URL"
label="Job posting URL"
inputId="job-link"
name="job-link"
placeholder="https://yourcompany.com/careers/senior-engineer"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ReactElement, ReactNode } from 'react';
import React from 'react';
import classNames from 'classnames';
import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button';
import { CopyIcon } from '../../icons';
import { IconSize } from '../../Icon';
import { useCopyLink } from '../../../hooks/useCopy';

export interface BrowserPreviewFrameProps {
url: string;
children: ReactNode;
className?: string;
}

export function BrowserPreviewFrame({
url,
children,
className,
}: BrowserPreviewFrameProps): ReactElement {
const [copying, copyLink] = useCopyLink(() => url);

return (
<div
className={classNames(
'flex flex-col overflow-hidden rounded-16 border border-border-subtlest-tertiary bg-background-default shadow-2',
className,
)}
>
<div className="flex items-center gap-2 border-b border-border-subtlest-tertiary bg-surface-float px-2 py-1">
<div className="flex shrink-0 items-center gap-1">
<div className="h-2 w-2 rounded-full bg-text-disabled" />
<div className="h-2 w-2 rounded-full bg-text-disabled" />
<div className="h-2 w-2 rounded-full bg-text-disabled" />
</div>

<div className="flex min-w-0 flex-1 items-center justify-center">
<div className="flex min-w-0 max-w-lg flex-1 items-center gap-1 rounded-4 bg-background-subtle px-2 py-0.5">
<span className="min-w-0 flex-1 truncate text-center text-xs text-text-tertiary">
{url}
</span>
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.XSmall}
icon={<CopyIcon size={IconSize.XSmall} />}
onClick={() => copyLink()}
className="shrink-0 !p-0"
title={copying ? 'Copied!' : 'Copy URL'}
/>
</div>
</div>

<div className="w-10 shrink-0" />
</div>

<div className="flex-1 overflow-y-auto p-4">{children}</div>
</div>
);
}

export default BrowserPreviewFrame;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import {
Typography,
TypographyType,
TypographyColor,
} from '../../typography/Typography';
import { EditIcon, EyeIcon } from '../../icons';
import { IconSize } from '../../Icon';

export enum EditPreviewTab {
Edit = 'edit',
Preview = 'preview',
}

export interface EditPreviewTabsProps {
activeTab: EditPreviewTab;
onTabChange: (tab: EditPreviewTab) => void;
className?: string;
}

export function EditPreviewTabs({
activeTab,
onTabChange,
className,
}: EditPreviewTabsProps): ReactElement {
const tabs = [
{
id: EditPreviewTab.Edit,
label: 'Edit',
icon: EditIcon,
},
{
id: EditPreviewTab.Preview,
label: 'Preview',
icon: EyeIcon,
},
];

return (
<div
className={classNames(
'z-10 sticky top-0 flex border-b border-border-subtlest-tertiary bg-background-default',
className,
)}
>
{tabs.map((tab) => {
const isActive = activeTab === tab.id;
const Icon = tab.icon;

return (
<button
key={tab.id}
type="button"
onClick={() => onTabChange(tab.id)}
className={classNames(
'flex flex-1 items-center justify-center gap-2 px-4 py-3 transition-colors',
isActive
? 'border-b-2 border-accent-cabbage-default text-text-primary'
: 'text-text-tertiary hover:text-text-secondary',
)}
>
<Icon
size={IconSize.Small}
className={isActive ? 'text-accent-cabbage-default' : undefined}
/>
<Typography
type={TypographyType.Callout}
color={
isActive ? TypographyColor.Primary : TypographyColor.Tertiary
}
bold={isActive}
>
{tab.label}
</Typography>
</button>
);
})}
</div>
);
}

export default EditPreviewTabs;
Loading