Skip to content

Commit e3e91c4

Browse files
committed
fix(apply-jobpost): incorporate feedback
- disable buttons whn form validation fails - add word count to rich text editor - fix minor design issues [Fixes #293]
1 parent 01d25fb commit e3e91c4

File tree

6 files changed

+120
-65
lines changed

6 files changed

+120
-65
lines changed

src/components/application/ApplicationFilter.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const ApplicationFilter: React.FC<ApplicationFilterProps> = ({
3131
<select value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}
3232
className="border dark:border-slate-400 p-[0.5rem] text-sm rounded-md dark:bg-dark-bg dark:text-white " >
3333
<option value="All">All Status</option>
34-
<option value="submitted">Submitted</option>
3534
<option value="under-review">Under Review</option>
3635
<option value="accepted">Accepted</option>
3736
<option value="rejected">Rejected</option>

src/pages/Applications/ApplicantApplication.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ export const ApplicantApplication = () => {
104104
<select className="sm:text-sm w-full sm:w-40 lg:w-32 h-10 rounded-bt-rd dark:bg-[#293647] dark:text-ltb" onChange={(e) => setStatus(e.target.value)}>
105105
<option value='All'>Sort by Status</option>
106106
<option value="All">All</option>
107-
<option value="submitted">Submitted</option>
108107
<option value="under-review">Under Review</option>
109108
<option value="accepted">Accepted</option>
110109
<option value="rejected">Rejected</option>

src/pages/Applications/ViewSingleApplication.tsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import WarningModal from '../../components/application/WarningModal'
77
import { ApplicationDetailsSkeleton } from '../../skeletons/applicationDetailsSkeleton';
88
import axiosClient from '../../redux/actions/axiosconfig'
99
import { getStatusClass } from "../../components/application/ApplicationStatus";
10+
import { IoClose } from "react-icons/io5";
1011

1112

1213
interface Application {
@@ -25,6 +26,7 @@ interface Application {
2526
title:string
2627
},
2728
status:string,
29+
comment:string,
2830
resume:string,
2931
essay:string,
3032
createdAt: string
@@ -36,6 +38,9 @@ const ApplicationDetails = () => {
3638
const [isLoading, setIsLoading] = useState(true);
3739
const [application, setApplication] = useState<Application | null>(null)
3840
const [status, setStatus] = useState("")
41+
const [toggleModal, setToggleModal] = useState(false)
42+
const [comment, setComment] = useState("")
43+
const [error, setError] = useState("")
3944

4045
const getJobApplication = async() => {
4146
const query = `query GetOneJobApplication($input: SingleJobApplicationInput!) {
@@ -55,6 +60,7 @@ const ApplicationDetails = () => {
5560
title
5661
}
5762
status
63+
comment
5864
essay
5965
resume
6066
createdAt
@@ -94,6 +100,12 @@ const ApplicationDetails = () => {
94100
if(!status){
95101
return
96102
}
103+
104+
if(status === 'rejected' && !comment){
105+
setError('Comment can not be empty')
106+
return
107+
}
108+
97109
const toastId = toast.loading('Updating status...');
98110
try{
99111
const mutation = `mutation ChangeApplicationStatus($input: StatusInput!) {
@@ -113,6 +125,7 @@ const ApplicationDetails = () => {
113125
title
114126
}
115127
status
128+
comment
116129
essay
117130
resume
118131
createdAt
@@ -126,7 +139,8 @@ const ApplicationDetails = () => {
126139
variables:{
127140
input:{
128141
applicationId:params.appId,
129-
status: status
142+
status: status,
143+
comment: comment
130144
}
131145
}
132146
},
@@ -140,6 +154,8 @@ const ApplicationDetails = () => {
140154

141155
toast.success('Status updated successfully')
142156
setApplication(response.data.data.changeApplicationStatus)
157+
setToggleModal(false)
158+
setComment("")
143159
}catch(err){
144160
toast.dismiss(toastId);
145161
toast.error('Failed to update status')
@@ -154,6 +170,11 @@ const ApplicationDetails = () => {
154170
},[])
155171

156172
useEffect(() => {
173+
if(status === 'rejected'){
174+
setToggleModal(true)
175+
return
176+
}
177+
157178
if(status && status !== application?.status){
158179
(async() => {
159180
await updateStatus()
@@ -164,6 +185,20 @@ const ApplicationDetails = () => {
164185

165186
return (
166187
<>
188+
{toggleModal && <div className="fixed top-0 left-0 flex items-center justify-center w-full h-screen bg-black bg-opacity-50 z-50">
189+
<div className="w-80 flex flex-col gap-4 rounded-md items-center bg-primary">
190+
<div className="flex py-2 items-center justify-between w-[90%]">
191+
<h2 className="text-white">Reject Applicant</h2>
192+
<IoClose color="white" size={20} onClick={() => {setStatus("");setToggleModal(false)}}/>
193+
</div>
194+
<div className="flex gap-2 flex-col w-[90%]">
195+
<label className="text-white text-sm">Comment</label>
196+
<textarea className="w-full p-1 rounded-sm h-32 bg-transparent border border-white text-white" onChange={(e) => setComment(e.target.value)}></textarea>
197+
<div className="text-sm text-red-500">{error}</div>
198+
</div>
199+
<button disabled={!comment} className={`rounded-md flex items-center justify-center bg-green ${comment ? 'opacity-100 cursor-pointer': 'opacity-70 cursor-not-allowed'} text-white px-4 py-1 mb-6`} onClick={updateStatus}>Submit</button>
200+
</div>
201+
</div>}
167202
<div className="flex flex-col min-w-full items-center dark:bg-dark-frame-bg min-h-screen">
168203
<div className="w-full max-w-6xl mt-10 p-6">
169204
{isLoading ? (
@@ -181,18 +216,19 @@ const ApplicationDetails = () => {
181216
<p className="text-gray-500 dark:text-gray-300"><span className="font-medium">Gender: </span> {application.userId?.gender}</p>
182217
<p className="text-gray-500 dark:text-gray-300"><span className="font-medium">Date of Submission:</span> {application.createdAt}</p>
183218
<p className="text-gray-500 dark:text-gray-300 font-medium"> Resume:{" "}<a href={application.resume} target="_blank" rel="noreferrer noopener" className="text-blue-500 font-normal hover:underline"> View Resume </a> </p>
184-
<div className="flex flex-col gap-4">
185-
<h2 className="text-lg text-white">Interest essay:</h2>
186-
<div className="text-white" dangerouslySetInnerHTML={{__html:application.essay}}></div>
187-
</div>
188219
<div className="flex items-center gap-2 text-white">
189-
<span className="font-medium">Status:</span>
220+
<span className="font-medium text-gray-500 dark:text-gray-300">Status:</span>
190221
<div className={`px-3 h-7 flex items-center justify-center rounded-full text-sm ${getStatusClass(application.status)}`}>{application.status}</div>
191222
</div>
223+
{application.comment && <p className="text-gray-500 dark:text-gray-300"><span className="font-medium">Comment:</span> {application.comment}</p>}
192224
</div>
193225
<div className="space-y-4">
194-
<h3 className="uppercase text-gray-800 dark:text-white font-bold">Job Post</h3>
195-
<p className="text-gray-500 dark:text-gray-300"><span className="font-medium">Title: </span> {application.jobId?.title}</p>
226+
<h3 className="uppercase text-gray-800 dark:text-white font-bold invisible">Candidate Information</h3>
227+
<p className="text-gray-500 dark:text-gray-300"><span className="font-medium">Job Title: </span> {application.jobId?.title}</p>
228+
<div className="flex flex-col gap-4 text-gray-500 dark:text-gray-300">
229+
<h2 className="text-base font-medium">Interest essay:</h2>
230+
<div dangerouslySetInnerHTML={{__html:application.essay}}></div>
231+
</div>
196232
</div>
197233
</div>
198234
</div>
@@ -204,11 +240,10 @@ const ApplicationDetails = () => {
204240
id=""
205241
value={status}
206242
onChange={(e) => setStatus(e.target.value)}
207-
disabled={isLoading}
208-
className="bg-[#10292C] flex items-center text-white justify-center dark:bg-white hover:bg-[#1f544cef] dark:text-zinc-700 font-bold py-2 px-4 rounded"
243+
disabled={isLoading || application?.status === 'rejected'}
244+
className={`bg-[#10292C] flex items-center text-white justify-center dark:bg-white ${application?.status === 'rejected' ? 'opacity-70 cursor-not-allowed': 'opacity-100 cursor-pointer'} hover:bg-[#1f544cef] dark:text-zinc-700 font-bold py-2 px-4 rounded ml-8`}
209245
>
210246
<option defaultChecked value="">Update status</option>
211-
<option value='submitted'>Submitted</option>
212247
<option value='under-review'>Under Review</option>
213248
<option value='accepted'>Accepted</option>
214249
<option value='rejected'>Rejected</option>

src/pages/ApplyJobPost.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,10 @@ const ApplyJobPost:React.FC = () => {
119119
const [resumeLoading, setResumeLoading] = useState(false)
120120
const [loading, setLoading] = useState(false)
121121
const {id} = useParams()
122+
const [wordCount, setWordCount] = useState(0)
122123
const navigate = useNavigate()
123124

124125

125-
126-
127126
const handleSubmit = async(data) => {
128127
const mutation = `
129128
mutation CreateNewJobApplication($input: JobApplicationInput!) {
@@ -222,7 +221,9 @@ const ApplyJobPost:React.FC = () => {
222221
}
223222
},
224223
onUpdate: ({ editor }) => {
225-
formik.setFieldValue('essay', editor.getHTML(), true)
224+
const words = editor.getHTML().trim()
225+
setWordCount(words.split(' ').length)
226+
formik.setFieldValue('essay', words, true)
226227
},
227228
onBlur: () => {
228229
formik.handleBlur('essay')
@@ -239,7 +240,7 @@ const ApplyJobPost:React.FC = () => {
239240
<ToolBar editor={editor}/>
240241
<EditorContent editor={editor} />
241242
</div>
242-
{/* <textarea className='w-full h-40 rounded-md border border-white bg-transparent' {...formik.getFieldProps('essay')}></textarea> */}
243+
<div className='text-sm'><span className={`${wordCount > 200 || wordCount === 0 ? 'text-red-500': 'text-green'}`}>{wordCount}</span> / 200 words</div>
243244
{formik.touched.essay && formik.errors.essay && <div className='text-sm text-red-500'>{formik.errors.essay}</div>}
244245
</div>
245246
<div className='flex flex-col gap-4'>
@@ -250,7 +251,7 @@ const ApplyJobPost:React.FC = () => {
250251
</div>
251252
{formik.touched.resume && formik.errors.resume && <div className='text-sm text-red-500'>{formik.errors.resume}</div>}
252253
</div>
253-
<button disabled={loading} className={`bg-green flex items-center justify-center self-center rounded-md w-28 h-10 mt-4 ${loading ? 'cursor-not-allowed':'cursor-pointer'}`}>{loading ? <Circles height={20} width={20} color='white'/> : 'Submit'}</button>
254+
<button disabled={loading} className={`${formik.isValid && formik.dirty ? 'bg-green cursor-pointer': 'bg-green opacity-70 cursor-not-allowed'} flex items-center justify-center self-center rounded-md w-28 h-10 mt-4`}>{loading ? <Circles height={20} width={20} color='white'/> : 'Submit'}</button>
254255
</form>
255256
</div>
256257
)

src/pages/JobPost/applicantJobFiltering.tsx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
} from "../../redux/actions/filterJobPost";
1919
import _ from "lodash"; // lodash for debounce
2020
import { debounce } from "lodash";
21+
import axiosClient from '../../redux/actions/axiosconfig'
22+
2123
const ApplicantSeachJobPost = (props: any) => {
2224
const { theme } = useTheme();
2325

@@ -57,6 +59,7 @@ const ApplicantSeachJobPost = (props: any) => {
5759
const [enteredWord, setEnteredWord] = useState("");
5860
const [filterAttribute, setFilterAttribute] = useState("");
5961
const [enteredsubmitWord, setenteredsubmitWord] = useState("");
62+
const [jobStatus, setJobStatus] = useState<{[key:string]: boolean} | null>(null)
6063
const input = {
6164
page: page + 1,
6265
itemsPerPage: itemsPerPage,
@@ -133,6 +136,60 @@ const ApplicantSeachJobPost = (props: any) => {
133136

134137
// console.log("JOB POST DATA =>>>>>: ", fetchJobPost);
135138

139+
const checkIfUserApplied = async(id:string) => {
140+
const query = `
141+
query CheckIfUserApplied($input: CheckIfUserAppliedInput!) {
142+
checkIfUserApplied(input: $input) {
143+
status
144+
}
145+
}
146+
`;
147+
148+
149+
const variables = {
150+
input: {
151+
jobId: id
152+
}
153+
};
154+
155+
try {
156+
const response = await axiosClient.post(
157+
'/',
158+
{
159+
query: query,
160+
variables: variables,
161+
},
162+
);
163+
164+
if(response.data.errors){
165+
toast.error(response.data.errors[0].message)
166+
return
167+
}
168+
169+
return response.data.data.checkIfUserApplied.status
170+
} catch (error) {
171+
console.log(error)
172+
return false
173+
}
174+
}
175+
176+
useEffect(() => {
177+
(async() => {
178+
for(const job of allfilteredjobPosts.data){
179+
const res = await checkIfUserApplied(job.id)
180+
setJobStatus(prevJobStatus => ({
181+
...prevJobStatus,
182+
[job.id]: res
183+
}));
184+
}
185+
})()
186+
},[allfilteredjobPosts])
187+
188+
useEffect(() => {
189+
console.log('AAAAAAAAAAAAAAAAAAAAAAAAAA', jobStatus)
190+
},[jobStatus])
191+
192+
136193
return (
137194
<>
138195
<ToastContainer />
@@ -267,11 +324,13 @@ const ApplicantSeachJobPost = (props: any) => {
267324
<td>
268325
<div className="flex flex-row gap-2 mt-2 justify-center">
269326
<Link
270-
to={`/applicant/available-job/${item.id}/apply`}
327+
to={`/applicant/available-job/${item.id}/apply?applied=${jobStatus && jobStatus[item.id]}`}
271328
replace
272329
>
273330
<button className="flex bg-primary dark:bg-[#56C870] rounded-md py-2 px-4 text-white font-medium cursor-pointer">
274-
Apply
331+
{
332+
jobStatus && jobStatus[item.id] ? 'View Details' : 'Apply'
333+
}
275334
</button>
276335
</Link>
277336
</div>
@@ -296,6 +355,7 @@ const ApplicantSeachJobPost = (props: any) => {
296355
Job POST
297356
</label>
298357
{allfilteredjobPosts?.data &&
358+
jobStatus &&
299359
allfilteredjobPosts?.data.length > 0 ? (
300360
allfilteredjobPosts?.data?.map((item: any) => (
301361
<div

src/pages/SubmitApplication.tsx

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { connect, useDispatch } from "react-redux";
66
import { showErrorToast, showSuccessToast } from "../utils/toast";
77
import axios from "axios";
88
import jwtDecode from "jwt-decode";
9-
import { Link } from "react-router-dom";
9+
import { Link, useSearchParams } from "react-router-dom";
1010
import { toast } from "react-toastify";
1111
import axiosClient from '../redux/actions/axiosconfig'
1212

@@ -22,51 +22,12 @@ const SubmitApplication: React.FC = (props: any) => {
2222
const { fetchSingleJobPostStates } = props;
2323
const dispatch = useDispatch();
2424
const { id } = useParams();
25+
const [searchParams, setSearchParams] = useSearchParams()
26+
const applied = searchParams.get('applied') === 'true'
2527
const loggedUser:data | null = localStorage.getItem('access_token') ? jwtDecode(localStorage.getItem('access_token') as string) : null;
2628
const [loading, setLoading] = useState(false);
2729
const [hasApplied, setHasApplied] = useState(false)
2830
const navigate = useNavigate()
29-
30-
const checkIfUserApplied = async() => {
31-
const query = `
32-
query CheckIfUserApplied($input: CheckIfUserAppliedInput!) {
33-
checkIfUserApplied(input: $input) {
34-
status
35-
}
36-
}
37-
`;
38-
39-
const variables = {
40-
input: {
41-
jobId: id
42-
}
43-
};
44-
45-
try {
46-
const response = await axiosClient.post(
47-
'/',
48-
{
49-
query: query,
50-
variables: variables,
51-
},
52-
);
53-
54-
if(response.data.errors){
55-
toast.error(response.data.errors[0].message)
56-
return
57-
}
58-
59-
setHasApplied(response.data.data.checkIfUserApplied.status)
60-
} catch (error) {
61-
console.log(error)
62-
}
63-
}
64-
65-
useEffect(() => {
66-
(async() => {
67-
await checkIfUserApplied()
68-
})()
69-
},[])
7031

7132
useEffect(() => {
7233
dispatch(fetchSingleJobPost(id));
@@ -134,17 +95,17 @@ console.log(fetchSingleJobPostStates)
13495
</div>
13596

13697
{/* FORM */}
137-
<div className="flex justify-center w-full mb-8">
98+
{!applied && <div className="flex justify-center w-full mb-8">
13899

139100
<button
140101
onClick={handleClick}
141-
disabled={hasApplied}
102+
disabled={applied}
142103
className={`bg-primary dark:bg-[#56C870] rounded-md py-2 px-4
143104
text-white font-medium transition-opacity duration-200 ${hasApplied ? 'cursor-not-allowed':'cursor-pointer'}`}
144105
>
145106
Apply here
146107
</button>
147-
</div>
108+
</div>}
148109
</div>
149110
)}
150111
</div>

0 commit comments

Comments
 (0)