diff --git a/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx b/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx index 5e0f4b92e4d..3d635442cdc 100644 --- a/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx +++ b/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx @@ -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); diff --git a/packages/shared/src/features/profile/components/experience/forms/UserCertificationForm.spec.tsx b/packages/shared/src/features/profile/components/experience/forms/UserCertificationForm.spec.tsx index 1fb62677212..10ff18dbb6b 100644 --- a/packages/shared/src/features/profile/components/experience/forms/UserCertificationForm.spec.tsx +++ b/packages/shared/src/features/profile/components/experience/forms/UserCertificationForm.spec.tsx @@ -33,7 +33,7 @@ const FormWrapper: React.FC = ({ defaultValues: { title: '', customCompanyName: '', - currentPosition: false, + current: false, startedAt: null, endedAt: null, externalReferenceId: '', @@ -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', diff --git a/packages/shared/src/hooks/useRemoveExperience.ts b/packages/shared/src/hooks/useRemoveExperience.ts index 26941316ef3..e661e906f9f 100644 --- a/packages/shared/src/hooks/useRemoveExperience.ts +++ b/packages/shared/src/hooks/useRemoveExperience.ts @@ -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 }); }, onError: () => { displayToast(labels.error.generic || 'Failed to delete experience'); diff --git a/packages/shared/src/hooks/useUserExperienceForm.spec.tsx b/packages/shared/src/hooks/useUserExperienceForm.spec.tsx index 9b1f4dd1aed..1c93e267b6a 100644 --- a/packages/shared/src/hooks/useUserExperienceForm.spec.tsx +++ b/packages/shared/src/hooks/useUserExperienceForm.spec.tsx @@ -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', @@ -59,6 +73,8 @@ type BaseUserExperience = { subtitle?: string | null; current?: boolean; companyId?: string | null; + customCompanyName?: string | null; + url?: string | null; }; describe('useUserExperienceForm', () => { diff --git a/packages/shared/src/hooks/useUserExperienceForm.ts b/packages/shared/src/hooks/useUserExperienceForm.ts index 6acc947aa0e..c06fe57d022 100644 --- a/packages/shared/src/hooks/useUserExperienceForm.ts +++ b/packages/shared/src/hooks/useUserExperienceForm.ts @@ -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'; @@ -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({ @@ -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 @@ -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 | null>(null); const router = useRouter(); const { displayToast } = useToastNotification(); @@ -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 }); router.push(`${webappUrl}settings/profile/experience/${type}`); - methods.reset(); }, onError: (error: ApiErrorResult) => { if ( diff --git a/packages/shared/src/hooks/useUserInfoForm.ts b/packages/shared/src/hooks/useUserInfoForm.ts index a46d68704aa..93c5bbe0827 100644 --- a/packages/shared/src/hooks/useUserInfoForm.ts +++ b/packages/shared/src/hooks/useUserInfoForm.ts @@ -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'; @@ -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; @@ -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(); @@ -105,6 +108,11 @@ const useUserInfoForm = (): UseUserInfoForm => { }), onSuccess: async (_, vars) => { + const oldProfileData = qc.getQueryData(userQueryKey); + qc.setQueryData(userQueryKey, { + ...oldProfileData, + ...vars, + }); await updateUser({ ...user, ...vars }); dirtyFormRef.current?.allowNavigation(); diff --git a/packages/webapp/pages/settings/profile/experience/edit.tsx b/packages/webapp/pages/settings/profile/experience/edit.tsx index a586b25c55a..fdab8897aaa 100644 --- a/packages/webapp/pages/settings/profile/experience/edit.tsx +++ b/packages/webapp/pages/settings/profile/experience/edit.tsx @@ -113,6 +113,7 @@ export const getServerSideProps: GetServerSideProps = async ({ startedAtYear, endedAtMonth, endedAtYear, + current: !result?.endedAt, skills: result?.skills.map((skill) => skill.value), location: result?.location, locationId: result?.location?.id || '', @@ -121,7 +122,10 @@ export const getServerSideProps: GetServerSideProps = async ({ }; }; -const renderExperienceForm = (type?: UserExperienceType) => { +const renderExperienceForm = ( + type?: UserExperienceType, + experience?: DefaultValues, +) => { switch (type) { case UserExperienceType.Education: return ; @@ -133,7 +137,7 @@ const renderExperienceForm = (type?: UserExperienceType) => { case UserExperienceType.OpenSource: return ; default: - return ; + return ; } }; @@ -163,7 +167,7 @@ const Page = ({ experience }: PageProps): ReactElement => { } > - {renderExperienceForm(experience?.type)} + {renderExperienceForm(experience?.type, experience)} {experience?.id && (