Skip to content
Open
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
10 changes: 10 additions & 0 deletions packages/shared/__tests__/fixture/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SettingsContextData } from '../../src/contexts/SettingsContext';
import { ThemeMode } from '../../src/contexts/SettingsContext';
import { SortCommentsBy } from '../../src/graphql/comments';

export const createTestSettings = (
props: Partial<SettingsContextData> = {},
Expand All @@ -19,7 +20,9 @@ export const createTestSettings = (
optOutCompanion: true,
optOutReadingStreak: true,
sidebarExpanded: true,
companionExpanded: false,
sortingEnabled: true,
sortCommentsBy: SortCommentsBy.NewestFirst,
showFeedbackButton: true,
toggleShowFeedbackButton: jest.fn(),
toggleAutoDismissNotifications: jest.fn(),
Expand All @@ -29,6 +32,13 @@ export const createTestSettings = (
toggleSortingEnabled: jest.fn(),
syncSettings: jest.fn(),
updateCustomLinks: jest.fn(),
updateSortCommentsBy: jest.fn(),
updateFlag: jest.fn(),
updateFlagRemote: jest.fn(),
updatePromptFlag: jest.fn(),
onToggleHeaderPlacement: jest.fn(),
setSettings: jest.fn(),
applyThemeMode: jest.fn(),
...props,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,19 @@ export function opportunityToFormData(
title: opportunity.title || '',
tldr: opportunity.tldr || '',
keywords: opportunity.keywords?.map((k) => ({ keyword: k.keyword })) || [],
externalLocationId: opportunity.locations?.[0]?.location?.city || undefined,
locationType: opportunity.locations?.[0]?.type,
locationData: opportunity.locations?.[0]?.location
? {
id: '',
city: opportunity.locations[0].location.city,
country: opportunity.locations[0].location.country || '',
subdivision: opportunity.locations[0].location.subdivision,
}
: undefined,
locations:
opportunity.locations?.map((loc) => ({
externalLocationId: undefined,
locationType: loc.type,
locationData: loc.location
? {
id: '',
city: loc.location.city,
country: loc.location.country || '',
subdivision: loc.location.subdivision,
}
: undefined,
})) || [],
meta: {
employmentType: opportunity.meta?.employmentType ?? 0,
teamSize: opportunity.meta?.teamSize ?? 1,
Expand Down Expand Up @@ -123,20 +126,19 @@ export function formDataToPreviewOpportunity(
title: formData.title,
tldr: formData.tldr,
keywords: formData.keywords,
locations: formData.locationType
? [
{
type: formData.locationType,
location: formData.locationData
? {
city: formData.locationData.city,
country: formData.locationData.country,
subdivision: formData.locationData.subdivision,
}
: null,
},
]
: undefined,
locations:
formData.locations
?.filter((loc) => loc.locationType)
.map((loc) => ({
type: loc.locationType,
location: loc.locationData
? {
city: loc.locationData.city,
country: loc.locationData.country,
subdivision: loc.locationData.subdivision,
}
: null,
})) || [],
meta: formData.meta
? {
employmentType: formData.meta.employmentType,
Expand Down Expand Up @@ -186,8 +188,15 @@ export function formDataToMutationPayload(
title: formData.title,
tldr: formData.tldr,
keywords: formData.keywords,
externalLocationId: formData.externalLocationId,
locationType: formData.locationType,
location: formData.locations
?.filter((loc) => loc.locationType)
.map((loc) => ({
externalLocationId: loc.externalLocationId,
type: loc.locationType,
city: loc.locationData?.city,
country: loc.locationData?.country,
subdivision: loc.locationData?.subdivision,
})),
meta: {
employmentType: formData.meta.employmentType,
teamSize: formData.meta.teamSize,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import { TextField } from '../../../fields/TextField';
import Textarea from '../../../fields/Textarea';
import {
Expand All @@ -14,6 +14,11 @@ import type { TLocation } from '../../../../graphql/autocomplete';
import { LocationDataset } from '../../../../graphql/autocomplete';
import type { Opportunity } from '../../../../features/opportunity/types';
import type { OpportunitySideBySideEditFormData } from '../hooks/useOpportunityEditForm';
import { Button, ButtonSize, ButtonVariant } from '../../../buttons/Button';
import { PlusIcon } from '../../../icons/Plus';
import { TrashIcon } from '../../../icons/Trash';
import { IconSize } from '../../../Icon';
import { LocationType } from '../../../../features/opportunity/protobuf/util';

export interface RoleInfoSectionProps {
opportunity: Opportunity;
Expand All @@ -29,10 +34,20 @@ export function RoleInfoSection({
formState: { errors },
} = useFormContext<OpportunitySideBySideEditFormData>();

const { fields, append, remove } = useFieldArray({
control,
name: 'locations',
});

const handleLocationSelect = useCallback(
(location: TLocation | null) => {
(location: TLocation | null, index: number) => {
setValue(
'locationData',
`locations.${index}.externalLocationId`,
location?.id || undefined,
{ shouldDirty: true },
);
setValue(
`locations.${index}.locationData`,
location
? {
id: location.id,
Expand All @@ -47,6 +62,17 @@ export function RoleInfoSection({
[setValue],
);

const handleAddLocation = useCallback(() => {
append({ locationType: LocationType.REMOTE });
}, [append]);

const handleRemoveLocation = useCallback(
(index: number) => {
remove(index);
},
[remove],
);

return (
<div className="flex flex-col gap-4">
<div data-field-key="title">
Expand Down Expand Up @@ -110,24 +136,56 @@ export function RoleInfoSection({
/>
</div>

<div data-field-key="location">
<ProfileLocation
locationName="externalLocationId"
typeName="locationType"
dataset={LocationDataset.Internal}
defaultValue={
opportunity?.locations?.[0]?.location
? {
id: '',
city: opportunity.locations[0].location.city,
country: opportunity.locations[0].location.country || '',
subdivision: opportunity.locations[0].location.subdivision,
type: opportunity.locations[0].type,
<div data-field-key="locations" className="flex flex-col gap-3">
<Typography bold type={TypographyType.Caption1}>
Locations
</Typography>
{fields.map((field, index) => (
<div key={field.id} className="flex items-start gap-2">
<div className="flex-1">
<ProfileLocation
locationName={`locations.${index}.externalLocationId`}
typeName={`locations.${index}.locationType`}
dataset={LocationDataset.Internal}
defaultValue={
opportunity?.locations?.[index]?.location
? {
id: '',
city: opportunity.locations[index].location.city,
country:
opportunity.locations[index].location.country || '',
subdivision:
opportunity.locations[index].location.subdivision,
type: opportunity.locations[index].type,
}
: undefined
}
: undefined
}
onLocationSelect={handleLocationSelect}
/>
onLocationSelect={(location) =>
handleLocationSelect(location, index)
}
/>
</div>
{fields.length > 1 && (
<Button
type="button"
variant={ButtonVariant.Tertiary}
size={ButtonSize.Small}
icon={<TrashIcon size={IconSize.Small} />}
onClick={() => handleRemoveLocation(index)}
className="mt-6"
/>
)}
</div>
))}
<Button
type="button"
variant={ButtonVariant.Secondary}
size={ButtonSize.Small}
icon={<PlusIcon size={IconSize.Small} />}
onClick={handleAddLocation}
>
Add location
</Button>
</div>
</div>
);
Expand Down
28 changes: 17 additions & 11 deletions packages/shared/src/lib/schema/opportunity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,7 @@ const processSalaryValue = (val: unknown) => {
return val;
};

export const opportunityEditInfoSchema = z.object({
title: z.string().nonempty('Add a job title').max(240),
tldr: z.string().nonempty('Add a short description').max(480),
keywords: z
.array(
z.object({
keyword: z.string().nonempty(),
}),
)
.min(1, 'Add at least one skill')
.max(100),
const locationEntrySchema = z.object({
externalLocationId: z.string().optional(),
locationType: z.number().optional(),
locationData: z
Expand All @@ -35,6 +25,22 @@ export const opportunityEditInfoSchema = z.object({
})
.nullable()
.optional(),
});

export type LocationEntry = z.infer<typeof locationEntrySchema>;

export const opportunityEditInfoSchema = z.object({
title: z.string().nonempty('Add a job title').max(240),
tldr: z.string().nonempty('Add a short description').max(480),
keywords: z
.array(
z.object({
keyword: z.string().nonempty(),
}),
)
.min(1, 'Add at least one skill')
.max(100),
locations: z.array(locationEntrySchema).optional().default([]),
meta: z.object({
employmentType: z.coerce.number().min(1, 'Select an employment type'),
teamSize: z
Expand Down