Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

JobForm.jsx 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import propTypes from 'prop-types';
  2. import img from '../../assets/Group 305.png';
  3. import * as Yup from 'yup';
  4. import { Formik, Form, ErrorMessage } from 'formik';
  5. import { JobFormContext } from '../../context';
  6. import { useContext, useState, useRef, useEffect } from 'react';
  7. import * as emailjs from 'emailjs-com';
  8. import { motion } from 'framer-motion';
  9. import ReactGA from 'react-ga4';
  10. import MyDropzone from './MyDropzone';
  11. import HashPositions from './HashPositions';
  12. import ReCAPTCHA from 'react-google-recaptcha';
  13. import axios from 'axios';
  14. import { UIContext } from '../../context/UIContextProvider';
  15. const api_url = process.env.REACT_APP_API_URL;
  16. export default function JobForm(props) {
  17. const [btnText, setBtnText] = useState('');
  18. const [sucMsg, setSucMsg] = useState(false);
  19. const captchaRef = useRef(null);
  20. const [msgText, setMsgText] = useState('');
  21. const cntCareersJobs = props.cntCareers;
  22. let defaultPositionSelection = props.defaultPositionSelection;
  23. //search context for prevous entry TODO
  24. const { jobForm, setJobForm } = useContext(UIContext);
  25. const [otherInputState, setOtherInputState] = useState(true);
  26. const [selectedPosition, setSelectedPosition] = useState(jobForm.position);
  27. //console.log(selectedPosition);
  28. const [errorMsg, setErrorMsg] = useState('');
  29. const [errorMsgPosition, setErrorMsgPosition] = useState('');
  30. const fileInput = useRef();
  31. function changeFormHandler(event) {
  32. const { name, value } = event.target;
  33. setJobForm(prevState => ({
  34. ...prevState,
  35. [name]: value,
  36. }));
  37. }
  38. function dropzoneToFormData(files) {
  39. setJobForm(prevState => ({
  40. ...prevState,
  41. file: files,
  42. }));
  43. }
  44. function hashToFormData(selected, other) {
  45. setJobForm(prevState => ({
  46. ...prevState,
  47. position: selected,
  48. other: other,
  49. }));
  50. }
  51. useEffect(() => {
  52. setJobForm(prevState => ({
  53. ...prevState,
  54. position: selectedPosition,
  55. }));
  56. },[selectedPosition])
  57. useEffect(() => {
  58. setBtnText(props.cta);
  59. }, [props.cta]);
  60. useEffect(() => {
  61. if (jobForm.file !== '') {
  62. setErrorMsg('');
  63. }
  64. }, [jobForm.file]);
  65. const validationSchema = Yup.object({
  66. email: Yup.string().email('Invalid email format').required('Email is Required'),
  67. firstName: Yup.string()
  68. .min(2, 'First name too short')
  69. .max(50, 'First name too long')
  70. .required('First Name is Required'),
  71. lastName: Yup.string()
  72. .min(2, 'Last name too short')
  73. .max(50, 'Last name too long')
  74. .required('Last name is Required'),
  75. coverLetter: Yup.string()
  76. .trim()
  77. .min(2, 'Cover Letter too short')
  78. .required('Cover Letter is Required'),
  79. other: Yup.string(),
  80. });
  81. return (
  82. <div className="mt-10 sm:mt-0 mx-auto">
  83. <div className="md:grid md:grid-cols-2 md:gap-6">
  84. <motion.div
  85. className="mt-5 md:mt-0 md:col-span-1"
  86. initial={{ x: -60, opacity: 0 }}
  87. animate={{ x: 0, opacity: 1 }}
  88. exit={{ x: -60, opacity: 0 }}
  89. transition={{ duration: 0.3, ease: 'easeOut' }}
  90. >
  91. <Formik
  92. initialValues={jobForm}
  93. validationSchema={validationSchema}
  94. onChange={changeFormHandler}
  95. onSubmit={async values => {
  96. const prepFile = async file => {
  97. if (file.size >= 2000000) {
  98. setErrorMsg('File too large!');
  99. return null;
  100. } else {
  101. const base64 = await convertBase64(file);
  102. return base64;
  103. }
  104. };
  105. const convertBase64 = file => {
  106. return new Promise((resolve, reject) => {
  107. const fileReader = new FileReader();
  108. fileReader.readAsDataURL(file);
  109. fileReader.onload = () => {
  110. resolve(fileReader.result);
  111. };
  112. fileReader.onerror = error => {
  113. reject(error);
  114. };
  115. });
  116. };
  117. if (
  118. selectedPosition === '' ||
  119. (otherInputState === false && values.other === '')
  120. )
  121. setErrorMsgPosition('Position is Required');
  122. else {
  123. setSelectedPosition(jobForm.position);
  124. setErrorMsgPosition('');
  125. if (jobForm.file === '') {
  126. setErrorMsg('CV is Required');
  127. } else {
  128. setErrorMsg('');
  129. if (jobForm.file.size >= 2000000) {
  130. setErrorMsg('File too large!');
  131. } else {
  132. const file = {
  133. filename: 'CV.pdf',
  134. data: jobForm.file,
  135. };
  136. const token = captchaRef.current.getValue();
  137. captchaRef.current.reset();
  138. if (token.length === 0) {
  139. setSucMsg(true);
  140. setMsgText('Please fill reCAPTCHA and try again. Thank you!');
  141. } else {
  142. await axios
  143. .post(`${process.env.REACT_APP_CAPTCHA_API}/verify-token`, {
  144. token,
  145. })
  146. .then(res => {
  147. setSucMsg(true);
  148. if (res.data.data.success) {
  149. setMsgText('Submission Succesful! Thank you!');
  150. values.position = jobForm.position;
  151. // props.mg.messages.create('dilig.net', {
  152. // from: `${values.firstName} ${values.lastName} <${values.email}>`,
  153. // to: ['hr@dilig.net'],
  154. // subject: 'Applying for a position',
  155. // text: `Email: ${values.email}, Position:${selectedPosition} ${values.other}, Cover letter: ${values.coverLetter}, Link: ${values.link} `,
  156. // html: `<p>Email: ${values.email}</p><p>Position: ${selectedPosition} ${values.other}</p><p>Cover letter: ${values.coverLetter}</p><p>Link (optional): ${values.link}</p>`,
  157. // attachment: file,
  158. // });
  159. console.log(jobForm);
  160. } else
  161. setMsgText('Please fill reCAPTCHA and try again. Thank you!');
  162. })
  163. .catch(error => {
  164. console.log(error);
  165. });
  166. }
  167. }
  168. }
  169. }
  170. ReactGA.event('contact', {
  171. category: 'Contact',
  172. action: 'Job application',
  173. });
  174. }}
  175. >
  176. {props => (
  177. <Form onSubmit={props.handleSubmit}>
  178. <div className="sm:rounded-md">
  179. <div className="py-2 sm:py-6">
  180. <div className="">
  181. <div className="col-span-1 sm:col-span-1">
  182. <div className="hidden py-1">
  183. <label
  184. htmlFor="first-name"
  185. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  186. >
  187. Position
  188. </label>
  189. <input
  190. type="text"
  191. name="position"
  192. id="position"
  193. onBlur={changeFormHandler}
  194. value={selectedPosition}
  195. onChange={props.handleChange}
  196. autoComplete="given-name"
  197. className="mt-1 disabled:bg-gray-100 disabled:border-gray-300 focus:ring-dg-primary-600 focus:border-dg-primary-900 block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  198. />
  199. <div className="h-4">
  200. <ErrorMessage
  201. name="position"
  202. component="div"
  203. className="text-sm text-right text-red-600"
  204. />
  205. </div>
  206. </div>
  207. {/* <RadioBoxesGroup/> */}
  208. <HashPositions
  209. changeFormHandler={changeFormHandler}
  210. cntCareers={cntCareersJobs}
  211. otherInputState={otherInputState}
  212. hashToFormData={hashToFormData}
  213. setOtherInputState={setOtherInputState}
  214. defaultPositionSelection={defaultPositionSelection}
  215. selectedPosition={selectedPosition}
  216. setSelectedPosition={setSelectedPosition}
  217. />
  218. <div className="py-1">
  219. <label
  220. htmlFor="first-name"
  221. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  222. >
  223. Other
  224. </label>
  225. <input
  226. disabled={otherInputState ? 'disabled' : ''}
  227. type="text"
  228. name="other"
  229. id="other"
  230. value={props.values.other}
  231. onChange={props.handleChange}
  232. className="mt-1 disabled:bg-gray-100 disabled:border-gray-300 dark:disabled:bg-gray-400 dark:disabled:border-gray-600 focus:ring-dg-primary-600 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  233. />
  234. </div>
  235. {errorMsgPosition != '' ? (
  236. <div className="h-4">
  237. <div className="text-sm text-right text-red-600">
  238. {errorMsgPosition}
  239. </div>
  240. </div>
  241. ) : null}
  242. <div className="py-1">
  243. <label
  244. htmlFor="first-name"
  245. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  246. >
  247. First name
  248. </label>
  249. <input
  250. type="text"
  251. name="firstName"
  252. id="firstName"
  253. onBlur={changeFormHandler}
  254. value={props.values.firstName}
  255. onChange={props.handleChange}
  256. autoComplete="name"
  257. autofill="true"
  258. className="mt-1 focus:ring-dg-primary-600 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  259. />
  260. <div className="h-4">
  261. <ErrorMessage
  262. name="firstName"
  263. component="div"
  264. className="text-sm text-right text-red-600"
  265. />
  266. </div>
  267. </div>
  268. <div className="py-1">
  269. <label
  270. htmlFor="last-name"
  271. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  272. >
  273. Last name
  274. </label>
  275. <input
  276. onBlur={changeFormHandler}
  277. type="text"
  278. name="lastName"
  279. id="lastName"
  280. value={props.values.lastName}
  281. onChange={props.handleChange}
  282. autoComplete="family-name"
  283. autofill="true"
  284. className="mt-1 focus:ring-dg-primary-900 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  285. />
  286. <div className="h-4">
  287. <ErrorMessage
  288. name="lastName"
  289. component="div"
  290. className="text-sm text-right text-red-600"
  291. />
  292. </div>
  293. </div>
  294. <div className="py-1">
  295. <label
  296. htmlFor="email"
  297. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  298. >
  299. Email
  300. </label>
  301. <input
  302. onBlur={changeFormHandler}
  303. type="email"
  304. name="email"
  305. id="email"
  306. value={props.values.email}
  307. onChange={props.handleChange}
  308. autoComplete="email"
  309. autofill="true"
  310. className="mt-1 focus:ring-dg-primary-900 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  311. />
  312. <div className="h-4">
  313. <ErrorMessage
  314. name="email"
  315. component="div"
  316. className="text-sm text-right text-red-600"
  317. />
  318. </div>
  319. </div>
  320. {/* <div className="form-check py-2">
  321. <input
  322. className="form-check-input appearance-none h-6 w-6 border text-dg-primary-900 border-dg-primary-600 rounded-sm bg-white checked:dg-primary-900 checked:border-dg-primary-900 focus:outline-none transition duration-200 align-top bg-no-repeat bg-center bg-contain float-left cursor-pointer focus:ring-dg-primary-900"
  323. type="checkbox"
  324. value=""
  325. id="meeting"
  326. />
  327. <label
  328. className="ml-3 form-check-label inline-block text-gray-800"
  329. htmlFor="meeting"
  330. >
  331. Schedule a meeting right away
  332. </label>
  333. </div> */}
  334. <div className="py-1">
  335. <label
  336. htmlFor="coverLetter"
  337. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  338. >
  339. Cover Letter
  340. </label>
  341. <textarea
  342. onBlur={changeFormHandler}
  343. type="text"
  344. placeholder="Why would you like to join us?"
  345. className="resize-y min-h-12 h-32 rounded-md mt-1 text-base focus:ring-dg-primary-900 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white sm:text-sm border-dg-primary-600 block w-full shadow-sm transition duration-200"
  346. name="coverLetter"
  347. id="coverLetter"
  348. value={props.values.coverLetter}
  349. onChange={props.handleChange}
  350. ></textarea>
  351. <div className="h-4">
  352. <ErrorMessage
  353. name="coverLetter"
  354. component="div"
  355. className="text-sm text-right text-red-600"
  356. />
  357. </div>
  358. </div>
  359. <div className="py-1">
  360. <label
  361. htmlFor="link"
  362. className="block text-sm font-medium text-gray-700 dark:text-gray-400"
  363. >
  364. Website link or Portfolio (optional)
  365. </label>
  366. <input
  367. type="url"
  368. name="link"
  369. id="link"
  370. onBlur={changeFormHandler}
  371. value={props.values.link}
  372. onChange={props.handleChange}
  373. className="mt-1 focus:ring-dg-primary-600 focus:border-dg-primary-900 dark:bg-dg-primary-1500 dark:text-white block w-full shadow-sm sm:text-sm border-dg-primary-600 rounded-md transition duration-200"
  374. />
  375. <div className="h-4">
  376. <ErrorMessage
  377. name="link"
  378. component="div"
  379. className="text-sm text-right text-red-600"
  380. />
  381. </div>
  382. </div>
  383. <div className="py-1 dark:text-gray-500">
  384. <MyDropzone
  385. dropzoneToFormData={dropzoneToFormData}
  386. props={props}
  387. fileInput={fileInput}
  388. />
  389. <div className="h-4">
  390. <div className="text-sm text-right">{errorMsg}</div>
  391. </div>
  392. </div>
  393. <div className="items-center justify-end flex">
  394. <ReCAPTCHA
  395. sitekey={process.env.REACT_APP_SITE_KEY}
  396. ref={captchaRef}
  397. />
  398. </div>
  399. <div className=" py-3 text-right">
  400. <button
  401. type="submit"
  402. className="btn btn_primary transition-all inline-flex justify-center py-4 px-14 border border-transparent shadow-md text-sm font-semibold rounded-xl text-white bg-dg-primary-600 hover:bg-dg-primary-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-dg-primary-600"
  403. >
  404. {btnText}
  405. </button>
  406. </div>
  407. {sucMsg && (
  408. <div className={'text-sm text-right text-dg-primary-900'}>
  409. {msgText}
  410. </div>
  411. )}
  412. </div>
  413. <div className="col-span-1 sm:col-span-1 lg:col-span-1 mt-8">
  414. Or contact us directly via email{' '}
  415. <a
  416. className="text-semibodld text-dg-primary-600"
  417. href="mailto:hr@dilig.net"
  418. >
  419. hr@dilig.net
  420. </a>
  421. </div>
  422. </div>
  423. </div>
  424. </div>
  425. </Form>
  426. )}
  427. </Formik>
  428. </motion.div>
  429. <motion.div
  430. className="mt-5 md:mt-0 md:col-span-1 flex items-center"
  431. initial={{ x: 60, opacity: 0 }}
  432. animate={{ x: 0, opacity: 1 }}
  433. exit={{ x: 60, opacity: 0 }}
  434. transition={{ duration: 0.3, ease: 'easeOut' }}
  435. >
  436. <img
  437. src={api_url + props.img.data.attributes.url}
  438. alt={props.img.data.attributes.alternativeText}
  439. />
  440. </motion.div>
  441. </div>
  442. </div>
  443. );
  444. }
  445. JobForm.propTypes = {
  446. defaultPositionSelection: propTypes.string,
  447. };