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 @@ -34,7 +34,9 @@ const DeleteExperienceButton = ({
experienceId,
experienceType,
}: DeleteExperienceButtonProps): React.ReactElement => {
const { removeExperience, isPending } = useRemoveExperience();
const { removeExperience, isPending } = useRemoveExperience({
type: experienceType,
});
const { showPrompt } = usePrompt();

const buttonCopy = getCopy(experienceType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const FormWrapper: React.FC<FormWrapperProps> = ({
defaultValues: {
title: '',
customCompanyName: '',
currentPosition: false,
current: false,
startedAt: null,
endedAt: null,
externalReferenceId: '',
Expand Down Expand Up @@ -157,7 +157,7 @@ describe('UserCertificationForm', () => {
const defaultValues = {
title: 'Google Cloud Professional Data Engineer',
customCompanyName: 'Google',
currentPosition: true,
current: true,
externalReferenceId: 'GCP-987654321',
url: 'https://example-cloud.com/certification/verify/987654321',
description: 'Expertise in data engineering on Google Cloud Platform',
Expand Down
17 changes: 12 additions & 5 deletions packages/shared/src/hooks/useRemoveExperience.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import type { UserExperienceType } from '../graphql/user/profile';
import { removeUserExperience } from '../graphql/user/profile';
import { useToastNotification } from './useToastNotification';
import { labels } from '../lib/labels';
import { webappUrl } from '../lib/constants';
import { useUserExperiencesByType } from '../features/profile/hooks/useUserExperiencesByType';
import { useAuthContext } from '../contexts/AuthContext';

const useRemoveExperience = () => {
const useRemoveExperience = ({ type }: { type: UserExperienceType }) => {
const { user } = useAuthContext();
const router = useRouter();
const { displayToast } = useToastNotification();

const { queryKey: experienceQueryKey } = useUserExperiencesByType(
type,
user?.id,
);
const qc = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: (id: string) => removeUserExperience(id),
onSuccess: () => {
const searchParams = new URLSearchParams(window.location.search);
const type = searchParams?.get('type');
router.push(`${webappUrl}/settings/profile/experience/${type}`);
qc.invalidateQueries({ queryKey: experienceQueryKey });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasoning as here

},
onError: () => {
displayToast(labels.error.generic || 'Failed to delete experience');
Expand Down
16 changes: 16 additions & 0 deletions packages/shared/src/hooks/useUserExperienceForm.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ jest.mock('../graphql/user/profile', () => ({
upsertUserGeneralExperience: jest.fn(),
}));

// Mock AuthContext
jest.mock('../contexts/AuthContext', () => ({
useAuthContext: jest.fn(() => ({
user: { id: 'test-user-id' },
})),
}));

// Mock useUserExperiencesByType hook
jest.mock('../features/profile/hooks/useUserExperiencesByType', () => ({
useUserExperiencesByType: jest.fn(() => ({
queryKey: ['user-experiences', 'test-type', 'test-user-id'],
})),
}));

const mockRouter = {
back: jest.fn(),
pathname: '/profile/experience',
Expand Down Expand Up @@ -59,6 +73,8 @@ type BaseUserExperience = {
subtitle?: string | null;
current?: boolean;
companyId?: string | null;
customCompanyName?: string | null;
url?: string | null;
};

describe('useUserExperienceForm', () => {
Expand Down
17 changes: 13 additions & 4 deletions packages/shared/src/hooks/useUserExperienceForm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
Expand All @@ -20,6 +20,8 @@ import { labels } from '../lib/labels';
import { applyZodErrorsToForm } from '../lib/form';
import { useToastNotification } from './useToastNotification';
import { webappUrl } from '../lib/constants';
import { useUserExperiencesByType } from '../features/profile/hooks/useUserExperiencesByType';
import { useAuthContext } from '../contexts/AuthContext';

export const userExperienceInputBaseSchema = z
.object({
Expand All @@ -28,7 +30,7 @@ export const userExperienceInputBaseSchema = z
description: z.string().max(5000).optional(),
subtitle: z.string().max(1000).optional(),
startedAt: z.date({ message: 'Start date is required.' }),
endedAt: z.date().optional(),
endedAt: z.date().optional().nullable(),
current: z.boolean().default(false),
companyId: z.string().nullable().optional().default(null),
customCompanyName: z
Expand Down Expand Up @@ -75,6 +77,12 @@ const useUserExperienceForm = ({
}: {
defaultValues: BaseUserExperience;
}) => {
const qc = useQueryClient();
const { user } = useAuthContext();
const { queryKey: experienceQueryKey } = useUserExperiencesByType(
defaultValues.type,
user?.id,
);
const dirtyFormRef = useRef<ReturnType<typeof useDirtyForm> | null>(null);
const router = useRouter();
const { displayToast } = useToastNotification();
Expand All @@ -88,10 +96,11 @@ const useUserExperienceForm = ({
type === UserExperienceType.Work
? upsertUserWorkExperience(data as UserExperienceWork, id)
: upsertUserGeneralExperience(data, id),
onSuccess: () => {
onSuccess: (_, vars) => {
methods.reset(vars);
dirtyFormRef.current?.allowNavigation();
qc.invalidateQueries({ queryKey: experienceQueryKey });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I invalidate here because its only used in settings pages for now, and with the rare updates its easier to just invalidate than map through all the edges and find the one that's been edited

router.push(`${webappUrl}settings/profile/experience/${type}`);
methods.reset();
},
onError: (error: ApiErrorResult) => {
if (
Expand Down
14 changes: 11 additions & 3 deletions packages/shared/src/hooks/useUserInfoForm.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useContext, useRef } from 'react';
import { useForm } from 'react-hook-form';
import type { UseFormReturn } from 'react-hook-form';
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import AuthContext from '../contexts/AuthContext';
import { UPDATE_USER_PROFILE_MUTATION } from '../graphql/users';
import type { LoggedUser, UserProfile } from '../lib/user';
import type { LoggedUser, PublicProfile, UserProfile } from '../lib/user';
import { useToastNotification } from './useToastNotification';
import type { ResponseError } from '../graphql/common';
import { errorMessage, gqlClient } from '../graphql/common';
Expand All @@ -15,6 +15,7 @@ import { useActions } from './useActions';
import { getCompletionItems } from '../features/profile/components/ProfileWidgets/ProfileCompletion';
import { useLogContext } from '../contexts/LogContext';
import { LogEvent } from '../lib/log';
import { useProfile } from './profile/useProfile';

export interface ProfileFormHint {
portfolio?: string;
Expand Down Expand Up @@ -59,8 +60,10 @@ const socials = [
];

const useUserInfoForm = (): UseUserInfoForm => {
const { logEvent } = useLogContext();
const qc = useQueryClient();
const { user, updateUser } = useContext(AuthContext);
const { userQueryKey } = useProfile(user as PublicProfile);
const { logEvent } = useLogContext();
const { displayToast } = useToastNotification();
const { completeAction, checkHasCompleted, isActionsFetched } = useActions();
const router = useRouter();
Expand Down Expand Up @@ -105,6 +108,11 @@ const useUserInfoForm = (): UseUserInfoForm => {
}),

onSuccess: async (_, vars) => {
const oldProfileData = qc.getQueryData<PublicProfile>(userQueryKey);
qc.setQueryData(userQueryKey, {
...oldProfileData,
...vars,
});
await updateUser({ ...user, ...vars });

dirtyFormRef.current?.allowNavigation();
Expand Down
10 changes: 7 additions & 3 deletions packages/webapp/pages/settings/profile/experience/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({
startedAtYear,
endedAtMonth,
endedAtYear,
current: !result?.endedAt,
skills: result?.skills.map((skill) => skill.value),
location: result?.location,
locationId: result?.location?.id || '',
Expand All @@ -121,7 +122,10 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({
};
};

const renderExperienceForm = (type?: UserExperienceType) => {
const renderExperienceForm = (
type?: UserExperienceType,
experience?: DefaultValues,
) => {
switch (type) {
case UserExperienceType.Education:
return <UserEducationForm />;
Expand All @@ -133,7 +137,7 @@ const renderExperienceForm = (type?: UserExperienceType) => {
case UserExperienceType.OpenSource:
return <UserProjectExperienceForm />;
default:
return <UserWorkExperienceForm />;
return <UserWorkExperienceForm location={experience?.location} />;
}
};

Expand Down Expand Up @@ -163,7 +167,7 @@ const Page = ({ experience }: PageProps): ReactElement => {
</Button>
}
>
{renderExperienceForm(experience?.type)}
{renderExperienceForm(experience?.type, experience)}
{experience?.id && (
<DeleteExperienceButton
experienceId={experience.id}
Expand Down