mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-07 17:48:26 +08:00
[resumes][feat] drag and drop for file upload (#378)
* [resumes][feat] drag and drop for file upload * [resumes][chore] use .tsx instead for landing page * [resumes][feat] use expandable text for additionalnfo * [resumes][refactor] clean up submit form * [resumes][fix] fix file upload error * [feat][resumes] change button to Submit Resume * [resumes][fix] fix expandable text
This commit is contained in:
@ -30,6 +30,7 @@
|
|||||||
"next-auth": "~4.10.3",
|
"next-auth": "~4.10.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.36.1",
|
"react-hook-form": "^7.36.1",
|
||||||
"react-pdf": "^5.7.2",
|
"react-pdf": "^5.7.2",
|
||||||
"react-query": "^3.39.2",
|
"react-query": "^3.39.2",
|
||||||
|
@ -10,7 +10,7 @@ export default function ResumeExpandableText({
|
|||||||
children,
|
children,
|
||||||
}: ResumeExpandableTextProps) {
|
}: ResumeExpandableTextProps) {
|
||||||
const ref = useRef<HTMLSpanElement>(null);
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const [descriptionOverflow, setDescriptionOverflow] = useState(false);
|
const [descriptionOverflow, setDescriptionOverflow] = useState(false);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -20,29 +20,27 @@ export default function ResumeExpandableText({
|
|||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|
||||||
const onSeeActionClicked = () => {
|
const onSeeActionClicked = () => {
|
||||||
setDescriptionExpanded(!descriptionExpanded);
|
setIsExpanded((prevExpanded) => !prevExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<span
|
<span
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'whitespace-pre-wrap text-sm',
|
'whitespace-pre-wrap text-sm',
|
||||||
'line-clamp-3',
|
'line-clamp-3',
|
||||||
descriptionExpanded ? 'line-clamp-none' : '',
|
isExpanded ? 'line-clamp-none' : '',
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
{descriptionOverflow && (
|
{descriptionOverflow && (
|
||||||
<div className="flex flex-row">
|
<p
|
||||||
<div
|
className="mt-1 cursor-pointer text-xs text-indigo-500 hover:text-indigo-300"
|
||||||
className="text-xs text-indigo-500 hover:text-indigo-300"
|
|
||||||
onClick={onSeeActionClicked}>
|
onClick={onSeeActionClicked}>
|
||||||
{descriptionExpanded ? 'See Less' : 'See More'}
|
{isExpanded ? 'See Less' : 'See More'}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
export default function SubmissionGuidelines() {
|
||||||
|
return (
|
||||||
|
<div className="mb-4 text-left text-sm text-slate-700">
|
||||||
|
<h2 className="mb-2 text-xl font-medium">Submission Guidelines</h2>
|
||||||
|
<p>
|
||||||
|
Before you submit, please review and acknolwedge our
|
||||||
|
<span className="font-bold"> submission guidelines </span>
|
||||||
|
stated below.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-lg font-bold">• </span>
|
||||||
|
Ensure that you do not divulge any of your
|
||||||
|
<span className="font-bold"> personal particulars</span>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-lg font-bold">• </span>
|
||||||
|
Ensure that you do not divulge any
|
||||||
|
<span className="font-bold">
|
||||||
|
{' '}
|
||||||
|
company's proprietary and confidential information
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-lg font-bold">• </span>
|
||||||
|
Proof-read your resumes to look for grammatical/spelling errors.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -16,6 +16,7 @@ import { Spinner } from '@tih/ui';
|
|||||||
|
|
||||||
import ResumeCommentsSection from '~/components/resumes/comments/ResumeCommentsSection';
|
import ResumeCommentsSection from '~/components/resumes/comments/ResumeCommentsSection';
|
||||||
import ResumePdf from '~/components/resumes/ResumePdf';
|
import ResumePdf from '~/components/resumes/ResumePdf';
|
||||||
|
import ResumeExpandableText from '~/components/resumes/shared/ResumeExpandableText';
|
||||||
|
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
@ -158,7 +159,9 @@ export default function ResumeReviewPage() {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||||
/>
|
/>
|
||||||
|
<ResumeExpandableText>
|
||||||
{detailsQuery.data.additionalInfo}
|
{detailsQuery.data.additionalInfo}
|
||||||
|
</ResumeExpandableText>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex w-full flex-col py-4 lg:flex-row">
|
<div className="flex w-full flex-col py-4 lg:flex-row">
|
||||||
|
@ -240,10 +240,10 @@ export default function ResumeHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<button
|
<button
|
||||||
className="rounded-md bg-indigo-500 py-1 px-3 text-sm text-white"
|
className="rounded-md bg-indigo-500 py-1 px-3 text-sm font-medium text-white"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onSubmitResume}>
|
onClick={onSubmitResume}>
|
||||||
Submit
|
Submit Resume
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,9 @@ import clsx from 'clsx';
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import type { FileRejection } from 'react-dropzone';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
import type { SubmitHandler } from 'react-hook-form';
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { PaperClipIcon } from '@heroicons/react/24/outline';
|
import { PaperClipIcon } from '@heroicons/react/24/outline';
|
||||||
@ -16,11 +18,13 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
} from '@tih/ui';
|
} from '@tih/ui';
|
||||||
|
|
||||||
|
import type { FilterOption } from '~/components/resumes/browse/resumeConstants';
|
||||||
import {
|
import {
|
||||||
EXPERIENCE,
|
EXPERIENCE,
|
||||||
LOCATION,
|
LOCATION,
|
||||||
ROLE,
|
ROLE,
|
||||||
} from '~/components/resumes/browse/resumeConstants';
|
} from '~/components/resumes/browse/resumeConstants';
|
||||||
|
import SubmissionGuidelines from '~/components/resumes/submit-form/SubmissionGuidelines';
|
||||||
|
|
||||||
import { RESUME_STORAGE_KEY } from '~/constants/file-storage-keys';
|
import { RESUME_STORAGE_KEY } from '~/constants/file-storage-keys';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
@ -43,11 +47,20 @@ type IFormInput = {
|
|||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SubmitResumeForm() {
|
type SelectorType = 'experience' | 'location' | 'role';
|
||||||
const { data: session, status } = useSession();
|
type SelectorOptions = {
|
||||||
const resumeCreateMutation = trpc.useMutation('resumes.resume.user.create');
|
key: SelectorType;
|
||||||
const router = useRouter();
|
label: string;
|
||||||
|
options: Array<FilterOption>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectors: Array<SelectorOptions> = [
|
||||||
|
{ key: 'role', label: 'Role', options: ROLE },
|
||||||
|
{ key: 'experience', label: 'Experience Level', options: EXPERIENCE },
|
||||||
|
{ key: 'location', label: 'Location', options: LOCATION },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function SubmitResumeForm() {
|
||||||
const [resumeFile, setResumeFile] = useState<File | null>();
|
const [resumeFile, setResumeFile] = useState<File | null>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [invalidFileUploadError, setInvalidFileUploadError] = useState<
|
const [invalidFileUploadError, setInvalidFileUploadError] = useState<
|
||||||
@ -55,13 +68,9 @@ export default function SubmitResumeForm() {
|
|||||||
>(null);
|
>(null);
|
||||||
const [isDialogShown, setIsDialogShown] = useState(false);
|
const [isDialogShown, setIsDialogShown] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const { data: session, status } = useSession();
|
||||||
if (status !== 'loading') {
|
const router = useRouter();
|
||||||
if (session?.user?.id == null) {
|
const resumeCreateMutation = trpc.useMutation('resumes.resume.user.create');
|
||||||
router.push('/api/auth/signin');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [router, session, status]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -75,9 +84,38 @@ export default function SubmitResumeForm() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onFileDrop = useCallback(
|
||||||
|
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||||
|
if (fileRejections.length === 0) {
|
||||||
|
setInvalidFileUploadError('');
|
||||||
|
setResumeFile(acceptedFiles[0]);
|
||||||
|
setValue('file', acceptedFiles[0]);
|
||||||
|
} else {
|
||||||
|
setInvalidFileUploadError(FILE_UPLOAD_ERROR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
'application/pdf': ['.pdf'],
|
||||||
|
},
|
||||||
|
maxFiles: 1,
|
||||||
|
maxSize: FILE_SIZE_LIMIT_BYTES,
|
||||||
|
onDrop: onFileDrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status !== 'loading') {
|
||||||
|
if (session?.user?.id == null) {
|
||||||
|
router.push('/api/auth/signin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [router, session, status]);
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
|
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
|
||||||
if (resumeFile == null) {
|
if (resumeFile == null) {
|
||||||
console.error('Resume file is empty');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -103,62 +141,53 @@ export default function SubmitResumeForm() {
|
|||||||
url,
|
url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (error) => {
|
onError(error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled() {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess() {
|
||||||
router.push('/resumes');
|
router.push('/resumes/browse');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUploadFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const onClickClear = () => {
|
||||||
const file = event.target.files?.item(0);
|
|
||||||
if (file == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (file.type !== 'application/pdf' || file.size > FILE_SIZE_LIMIT_BYTES) {
|
|
||||||
setInvalidFileUploadError(FILE_UPLOAD_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setInvalidFileUploadError('');
|
|
||||||
setResumeFile(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickReset = () => {
|
|
||||||
if (isDirty || resumeFile != null) {
|
if (isDirty || resumeFile != null) {
|
||||||
setIsDialogShown(true);
|
setIsDialogShown(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickProceedDialog = () => {
|
const onClickResetDialog = () => {
|
||||||
setIsDialogShown(false);
|
setIsDialogShown(false);
|
||||||
reset();
|
reset();
|
||||||
setResumeFile(null);
|
setResumeFile(null);
|
||||||
|
setInvalidFileUploadError(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickDownload = async () => {
|
const onClickDownload = async (
|
||||||
|
event: React.MouseEvent<HTMLParagraphElement, MouseEvent>,
|
||||||
|
) => {
|
||||||
if (resumeFile == null) {
|
if (resumeFile == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Prevent click event from propagating up to dropzone
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
const url = window.URL.createObjectURL(resumeFile);
|
const url = window.URL.createObjectURL(resumeFile);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.setAttribute('download', resumeFile.name);
|
link.setAttribute('download', resumeFile.name);
|
||||||
|
|
||||||
// Append to html link element page
|
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
|
|
||||||
// Start download
|
// Start download
|
||||||
link.click();
|
link.click();
|
||||||
|
|
||||||
// Clean up and remove the link
|
// Clean up and remove the link and object URL
|
||||||
link.remove();
|
link.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileUploadError = useMemo(() => {
|
const fileUploadError = useMemo(() => {
|
||||||
@ -179,6 +208,7 @@ export default function SubmitResumeForm() {
|
|||||||
<section
|
<section
|
||||||
aria-labelledby="primary-heading"
|
aria-labelledby="primary-heading"
|
||||||
className="flex h-full min-w-0 flex-1 flex-col lg:order-last">
|
className="flex h-full min-w-0 flex-1 flex-col lg:order-last">
|
||||||
|
{/* Reset Dialog component */}
|
||||||
<Dialog
|
<Dialog
|
||||||
isShown={isDialogShown}
|
isShown={isDialogShown}
|
||||||
primaryButton={
|
primaryButton={
|
||||||
@ -186,7 +216,7 @@ export default function SubmitResumeForm() {
|
|||||||
display="block"
|
display="block"
|
||||||
label="OK"
|
label="OK"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={onClickProceedDialog}
|
onClick={onClickResetDialog}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
secondaryButton={
|
secondaryButton={
|
||||||
@ -204,6 +234,7 @@ export default function SubmitResumeForm() {
|
|||||||
<div className="mx-20 space-y-4 py-8">
|
<div className="mx-20 space-y-4 py-8">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<h1 className="mb-4 text-2xl font-bold">Upload a resume</h1>
|
<h1 className="mb-4 text-2xl font-bold">Upload a resume</h1>
|
||||||
|
{/* Title Section */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<TextInput
|
<TextInput
|
||||||
{...register('title', { required: true })}
|
{...register('title', { required: true })}
|
||||||
@ -214,37 +245,20 @@ export default function SubmitResumeForm() {
|
|||||||
onChange={(val) => setValue('title', val)}
|
onChange={(val) => setValue('title', val)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
{/* Selectors */}
|
||||||
|
{selectors.map((item) => (
|
||||||
|
<div key={item.key} className="mb-4">
|
||||||
<Select
|
<Select
|
||||||
{...register('role', { required: true })}
|
{...register(item.key, { required: true })}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
label="Role"
|
label={item.label}
|
||||||
options={ROLE}
|
options={item.options}
|
||||||
required={true}
|
required={true}
|
||||||
onChange={(val) => setValue('role', val)}
|
onChange={(val) => setValue(item.key, val)}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Select
|
|
||||||
{...register('experience', { required: true })}
|
|
||||||
disabled={isLoading}
|
|
||||||
label="Experience Level"
|
|
||||||
options={EXPERIENCE}
|
|
||||||
required={true}
|
|
||||||
onChange={(val) => setValue('experience', val)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Select
|
|
||||||
{...register('location', { required: true })}
|
|
||||||
disabled={isLoading}
|
|
||||||
label="Location"
|
|
||||||
name="location"
|
|
||||||
options={LOCATION}
|
|
||||||
required={true}
|
|
||||||
onChange={(val) => setValue('location', val)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
{/* Upload Resume Section */}
|
||||||
<p className="text-sm font-medium text-slate-700">
|
<p className="text-sm font-medium text-slate-700">
|
||||||
Upload resume (PDF format)
|
Upload resume (PDF format)
|
||||||
<span aria-hidden="true" className="text-danger-500">
|
<span aria-hidden="true" className="text-danger-500">
|
||||||
@ -252,8 +266,10 @@ export default function SubmitResumeForm() {
|
|||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
{/* Upload Resume Box */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div
|
<div
|
||||||
|
{...getRootProps()}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
fileUploadError ? 'border-danger-600' : 'border-gray-300',
|
fileUploadError ? 'border-danger-600' : 'border-gray-300',
|
||||||
'mt-2 flex justify-center rounded-md border-2 border-dashed px-6 pt-5 pb-6',
|
'mt-2 flex justify-center rounded-md border-2 border-dashed px-6 pt-5 pb-6',
|
||||||
@ -276,20 +292,25 @@ export default function SubmitResumeForm() {
|
|||||||
<label
|
<label
|
||||||
className="rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2"
|
className="rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2"
|
||||||
htmlFor="file-upload">
|
htmlFor="file-upload">
|
||||||
<p className="cursor-pointer font-medium text-indigo-600 hover:text-indigo-500">
|
<div className="flex gap-1 ">
|
||||||
|
<p className="cursor-pointer font-medium text-indigo-600 hover:text-indigo-400">
|
||||||
{resumeFile == null
|
{resumeFile == null
|
||||||
? 'Upload a file'
|
? 'Upload a file'
|
||||||
: 'Replace file'}
|
: 'Replace file'}
|
||||||
</p>
|
</p>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
or drag and drop
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
{...register('file', { required: true })}
|
{...register('file', { required: true })}
|
||||||
|
{...getInputProps()}
|
||||||
accept="application/pdf"
|
accept="application/pdf"
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
name="file-upload"
|
name="file-upload"
|
||||||
type="file"
|
type="file"
|
||||||
onChange={onUploadFile}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -302,6 +323,7 @@ export default function SubmitResumeForm() {
|
|||||||
<p className="text-danger-600 text-sm">{fileUploadError}</p>
|
<p className="text-danger-600 text-sm">{fileUploadError}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Additional Info Section */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<TextArea
|
<TextArea
|
||||||
{...register('additionalInfo')}
|
{...register('additionalInfo')}
|
||||||
@ -311,58 +333,28 @@ export default function SubmitResumeForm() {
|
|||||||
onChange={(val) => setValue('additionalInfo', val)}
|
onChange={(val) => setValue('additionalInfo', val)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4 text-left text-sm text-slate-700">
|
{/* Submission Guidelines */}
|
||||||
<h2 className="mb-2 text-xl font-medium">
|
<SubmissionGuidelines />
|
||||||
Submission Guidelines
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
Before you submit, please review and acknolwedge our
|
|
||||||
<span className="font-bold"> submission guidelines </span>
|
|
||||||
stated below.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-lg font-bold">• </span>
|
|
||||||
Ensure that you do not divulge any of your
|
|
||||||
<span className="font-bold"> personal particulars</span>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-lg font-bold">• </span>
|
|
||||||
Ensure that you do not divulge any
|
|
||||||
<span className="font-bold">
|
|
||||||
{' '}
|
|
||||||
company's proprietary and confidential information
|
|
||||||
</span>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-lg font-bold">• </span>
|
|
||||||
Proof-read your resumes to look for grammatical/spelling
|
|
||||||
errors.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
{...register('isChecked', { required: true })}
|
{...register('isChecked', { required: true })}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
label="I have read and will follow the guidelines stated."
|
label="I have read and will follow the guidelines stated."
|
||||||
onChange={(val) => setValue('isChecked', val)}
|
onChange={(val) => setValue('isChecked', val)}
|
||||||
/>
|
/>
|
||||||
|
{/* Clear and Submit Buttons */}
|
||||||
<div className="mt-4 flex justify-end gap-4">
|
<div className="mt-4 flex justify-end gap-4">
|
||||||
<Button
|
<Button
|
||||||
addonPosition="start"
|
addonPosition="start"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
display="inline"
|
|
||||||
label="Clear"
|
label="Clear"
|
||||||
size="md"
|
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
onClick={onClickReset}
|
onClick={onClickClear}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
addonPosition="start"
|
addonPosition="start"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
display="inline"
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
label="Submit"
|
label="Submit"
|
||||||
size="md"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
/>
|
/>
|
||||||
|
21
yarn.lock
21
yarn.lock
@ -4605,6 +4605,11 @@ atob@^2.1.2:
|
|||||||
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz"
|
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
attr-accept@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||||
|
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||||
|
|
||||||
autoprefixer@^10.3.7, autoprefixer@^10.4.12, autoprefixer@^10.4.7:
|
autoprefixer@^10.3.7, autoprefixer@^10.4.12, autoprefixer@^10.4.7:
|
||||||
version "10.4.12"
|
version "10.4.12"
|
||||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz"
|
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz"
|
||||||
@ -7733,6 +7738,13 @@ file-loader@^6.0.0, file-loader@^6.2.0:
|
|||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
|
|
||||||
|
file-selector@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc"
|
||||||
|
integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
file-system-cache@^1.0.5:
|
file-system-cache@^1.0.5:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.1.0.tgz"
|
||||||
@ -12162,6 +12174,15 @@ react-dom@18.2.0, react-dom@^18.2.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.0"
|
scheduler "^0.23.0"
|
||||||
|
|
||||||
|
react-dropzone@^14.2.3:
|
||||||
|
version "14.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b"
|
||||||
|
integrity sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==
|
||||||
|
dependencies:
|
||||||
|
attr-accept "^2.2.2"
|
||||||
|
file-selector "^0.6.0"
|
||||||
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
react-element-to-jsx-string@^14.3.4:
|
react-element-to-jsx-string@^14.3.4:
|
||||||
version "14.3.4"
|
version "14.3.4"
|
||||||
resolved "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz"
|
resolved "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz"
|
||||||
|
Reference in New Issue
Block a user