Files
noahspan-portfolio/client/src/components/profileForm/ProfileForm.tsx
noahspannbauer e5219761c2
Some checks failed
Main / changes (push) Successful in 37s
Main / deploy (push) Has been cancelled
Main / build-and-test (push) Has been cancelled
adding skills to experiences, profiles, and projects
2026-07-20 19:28:37 -05:00

259 lines
9.3 KiB
TypeScript

import { useEffect, useReducer } from 'react';
import { useForm, Controller, FormProvider } from 'react-hook-form';
import { initialState, reducer } from './reducer';
import { ProfileFormProps } from './ProfileFormProps.interface';
import { FormMode } from '../../enums/formMode';
import httpClient from '../../httpClient/httpClient';
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons';
import { ScreenSize } from '../../enums/screenSize';
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { Skill } from '../skills/Skill.interface';
import { useSkills } from '../../hooks/skills/UseSkills';
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const { skills } = useSkills();
const projectSkills: Skill[] = []
const defaultValues = {
name: '',
headline: '',
summary: '',
userId: '',
skills: projectSkills,
statusId: 1
}
const methods = useForm({
defaultValues: defaultValues
});
// const watchRepoName = methods.watch(['repoName'])
const { screenSize } = useBreakpoints();
const onSetSelectedSkills = (selectedSkill: Skill) => {
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
let newSelectedSkills: Skill[];
if (selectedSkillExists) {
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
} else {
newSelectedSkills = [...state.selectedSkills, selectedSkill]
}
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
methods.setValue('skills', newSelectedSkills)
}
const onCancel = () => {
methods.reset(defaultValues);
dispatch({ type: 'SET_IS_DISABLED', payload: false });
onOpenClose(FormMode.CANCEL);
};
const onSubmit = async (data: any) => {
try {
dispatch({ type: 'SET_IS_LOADING', payload: true });
console.log(data)
if (!profileId) {
const newData = {
...data,
createdBy: appContext.state.userProfile.userPrincipalName,
userId: appContext.state.userProfile.userPrincipalName
}
console.log(newData)
await httpClient.post(`api/profiles`, newData);
} else {
const newData = {
...data,
updatedBy: appContext.state.userProfile.userPrincipalName
}
console.log(newData)
await httpClient.put(`api/profiles/${profileId}`, newData);
}
methods.reset(defaultValues);
dispatch({ type: 'SET_IS_DISABLED', payload: false });
onOpenClose(FormMode.CANCEL);
} catch (error) {
const axiosError = error as AxiosError;
console.log(axiosError)
dispatch({ type: 'SET_ERROR', payload: axiosError.message });
} finally {
dispatch({ type: 'SET_IS_LOADING', payload: false });
}
};
useEffect(() => {
if (mode === FormMode.VIEW) {
dispatch({ type: 'SET_IS_DISABLED', payload: true });
}
}, [mode]);
useEffect(() => {
const getProfile = async () => {
try {
dispatch({ type: 'SET_IS_LOADING', payload: true });
const response: AxiosResponse = await httpClient.get(
`api/profiles/${profileId}`
);
const profile = response.data;
if (profile.skills) {
dispatch({ type: 'SET_SELECTED_SKILLS', payload: profile.skills })
}
methods.reset(profile);
} catch (error) {
const axiosError = error as AxiosError;
console.log(axiosError)
dispatch({ type: 'SET_ERROR', payload: axiosError.message });
} finally {
dispatch({ type: 'SET_IS_LOADING', payload: false });
}
};
if (profileId && isDrawerOpen) {
getProfile();
}
}, [profileId]);
useEffect(() => {
if (skills) {
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
}
}, [skills])
return (
<div className='drawer drawer-end'>
<input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} />
<div className='drawer-side'>
<label
htmlFor='my-drawer-1'
aria-label='close-sidebar'
className='drawer-overlay'
></label>
<div className={`menu bg-base-100 text-base-content min-h-full p-4 ${screenSize === ScreenSize.SM ? 'w-full' : screenSize === ScreenSize.MD ? 'w-[66%]' : 'w-[33%]'}`}>
<FormProvider {...methods}>
<form className='prose max-w-none' onSubmit={methods.handleSubmit(onSubmit)} style={{ paddingBottom: '50px' }}>
<div className='grid grid-cols-12 gap-3'>
<div className="col-span-10">
<h2 className="mt-0 mb-0 self-center">
{`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Profile`}
</h2>
</div>
<div className="col-span-2 justify-self-end self-center">
<button className="btn btn-ghost" onClick={onCancel}>
<FontAwesomeIcon icon={faXmark} />
</button>
</div>
<div className='col-span-3 self-center'>
<span>Name *</span>
</div>
<div className='col-span-9'>
<Controller
name="name"
control={methods.control}
rules={{ required: 'A profile title is required' }}
render={({ field: { onChange, value } }) => (
<input
type='text'
className='input w-full'
disabled={state.isDisabled}
onChange={onChange}
value={value}
/>
)}
/>
</div>
<div className='col-span-3 self-center'>
<span>Headline</span>
</div>
<div className='col-span-9'>
<Controller
name='headline'
control={methods.control}
render={({ field: { onChange, value } }) => (
<input
type='text'
className='input w-full'
disabled={state.isDisabled}
onChange={onChange}
value={value}
/>
)}
/>
</div>
<div className='col-span-3 self-center'>
<span>Summary</span>
</div>
<div className='col-span-9'>
<Controller
name="summary"
control={methods.control}
render={({ field: { onChange, value } }) => (
<input
type='text'
className='input w-full'
disabled={state.isDisabled}
onChange={onChange}
value={value}
/>
)}
/>
</div>
<div className={`col-span-3 self-center after:content-['*'] after:ms-0.5`}>
<span>Skills</span>
</div>
<div className='col-span-9'>
<Controller
name="skills"
control={methods.control}
render={() => (
<MultiSelectDropdown
skills={state.skillOptions}
onChange={onSetSelectedSkills}
selectedSkills={state.selectedSkills}
/>
)}
/>
</div>
<div className='col-span-12 justify-self-end self-center'>
<button
className='btn'
disabled={
state.isDisabled && mode.toString() !== FormMode.VIEW
? state.isDisabled
: false
}
onClick={onCancel}
data-testid="profile-cancel-button"
>
<FontAwesomeIcon icon={faXmark} />
{mode.toString() !== FormMode.VIEW ? 'Cancel' : 'Close'}
</button>
{mode.toString() !== FormMode.VIEW && (
<button
className='btn btn-primary ml-2.5'
disabled={state.isDisabled}
type="submit"
data-testid="profile-save-button"
>
<FontAwesomeIcon icon={faSave} />
Save
</button>
)}
</div>
</div>
</form>
</FormProvider>
</div>
</div>
</div>
)
}
export default ProfileForm;