This is just a technology testing project based on Create React App and TailwindCSS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
9.4KB

  1. import React, { useEffect } from 'react'
  2. import PropTypes from 'prop-types'
  3. import { motion, AnimatePresence } from "framer-motion"
  4. import { Formik, Form } from 'formik'
  5. import { Persist } from 'formik-persist'
  6. import * as Yup from 'yup'
  7. import ReCAPTCHA from "react-google-recaptcha";
  8. import FormikControl from '../FormikControls'
  9. import ButtonPrimary from '../controls/ButtonPrimary'
  10. import { SubmitFormResult } from '../SubmitFormResult';
  11. export const FormFeedback = (props) => {
  12. const { setPopupTitle, setPopupContent, setPopupOpened } = props
  13. useEffect(() => {
  14. if (setPopupTitle) { setPopupTitle('Some feedback form example') }
  15. })
  16. const sections = [
  17. {
  18. label: 'User support',
  19. value: 'users'
  20. },
  21. {
  22. label: 'Partnership',
  23. value: 'partners'
  24. },
  25. ]
  26. const support_types = [
  27. {
  28. label: 'Consulting on product',
  29. value: 'product'
  30. },
  31. {
  32. label: 'Technical support',
  33. value: 'technical'
  34. },
  35. {
  36. label: 'Legal support',
  37. value: 'legal'
  38. },
  39. ]
  40. const support_types_values = support_types.map(val => val.value)
  41. const supportTypesMotionVariants = {
  42. hidden: {
  43. opacity: 0,
  44. scaleY: 0,
  45. transition: {
  46. type: "linear",
  47. duration: .2
  48. }
  49. },
  50. visible: {
  51. opacity: 1,
  52. scaleY: 1,
  53. transition: {
  54. type: "linear",
  55. duration: .2
  56. }
  57. },
  58. }
  59. const initialValues = {
  60. user_name: '',
  61. email: '',
  62. section: '',
  63. support_type: [],
  64. message: '',
  65. }
  66. const validationSchema = Yup.object({
  67. user_name: Yup.string()
  68. .required('Required'),
  69. email: Yup.string()
  70. .required('Required')
  71. .email('Must be a valid email address'),
  72. section: Yup.string()
  73. .required('Required')
  74. .oneOf(['users', 'partners'], 'Invalid section'),
  75. support_type: Yup.array(Yup.string()).ensure().when('section', {
  76. is: 'users',
  77. then: Yup.array(Yup.string()).min(1, 'Required')
  78. .test('is-valid-support-type', 'Invalid support type', val => {
  79. const sb = val.filter(n => !support_types_values.includes(n))
  80. return 0 === sb.length
  81. }
  82. ),
  83. }),
  84. message: Yup.string()
  85. .required('Required')
  86. .max(500, val => `Maximum ${val.max} symbols`),
  87. })
  88. const recaptchaRef = React.useRef();
  89. const onSubmitWithReCAPTCHA = async (values, { resetForm }) => {
  90. setPopupOpened(false)
  91. if (recaptchaRef.current && recaptchaRef.current.executeAsync) {
  92. let token
  93. try {
  94. await Promise.race([
  95. new Promise((_, reject) => setTimeout(() => {
  96. reject(new Error('timeout'))
  97. }, 5000)),
  98. recaptchaRef.current.executeAsync(),
  99. ])
  100. .then((v) => {
  101. token = v
  102. })
  103. } catch (error) {
  104. console.error('app error - catch in try around Promise.race', error)
  105. token = false
  106. }
  107. if (token) {
  108. values.recaptcha = token
  109. // /* // <-- Stub code begin!
  110. console.log('values', values)
  111. resetForm()
  112. resetForm() //api bug workaround - need to call reset twice
  113. setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
  114. setPopupOpened(true)
  115. // */ // <-- Stub code end!
  116. /* // <-- Example code begin!
  117. const res = await someApiCall(values)
  118. if ('success' === res.state) {
  119. resetForm()
  120. resetForm() //api bug workaround - need to call reset twice
  121. setFormState('success')
  122. return
  123. } else if ('error' === res.state) {
  124. setFormMessage(res.payload)
  125. } else {
  126. setFormMessage('Incorrect server response')
  127. }
  128. // */ // <-- Example code end!
  129. } else {
  130. console.error('app error - reCaptcha token is empty')
  131. alert('reCaptcha token is empty')
  132. }
  133. } else {
  134. console.error('app error - reCaptcha is not rendered')
  135. alert('reCaptcha is not rendered')
  136. }
  137. }
  138. return (
  139. <Formik
  140. initialValues={initialValues}
  141. validationSchema={validationSchema}
  142. onSubmit={onSubmitWithReCAPTCHA}
  143. >
  144. {formik => {
  145. return (
  146. <div className="overflow-y-auto max-h-screen-80">
  147. <Form className="feedback-form" autoComplete="off">
  148. <div className="p-3">
  149. <FormikControl
  150. control='string'
  151. name='user_name'
  152. label='Your name *'
  153. placeholder='Enter your name'
  154. autoFocus={true}
  155. />
  156. <FormikControl
  157. control='string'
  158. name='email'
  159. label='Email *'
  160. placeholder='Enter email for reply'
  161. />
  162. <FormikControl
  163. control='radio-group'
  164. name='section'
  165. label='Section *'
  166. options={sections}
  167. />
  168. <AnimatePresence>
  169. {'users' === formik.values.section &&
  170. <motion.div
  171. variants={supportTypesMotionVariants}
  172. initial="hidden"
  173. animate="visible"
  174. exit="hidden"
  175. >
  176. <FormikControl
  177. control='checkbox-group'
  178. name='support_type'
  179. label='Required types of support *'
  180. options={support_types}
  181. />
  182. </motion.div>
  183. }
  184. </AnimatePresence>
  185. <FormikControl
  186. control='text'
  187. name='message'
  188. label='Message *'
  189. placeholder='Enter your message'
  190. />
  191. </div>
  192. <div className="px-2 pt-4 pb-3 mb-0 bg-primary-200 border-t sm:rounded-b-lg">
  193. <ButtonPrimary type="submit" text="Send" disabled={!formik.isValid || !formik.dirty} />
  194. {(!formik.isValid || !formik.dirty)
  195. ? <div className="error text-center text-sm text-red-700 mt-1 mr-1">Please fill all necessary form fields</div>
  196. : <div className="text-gray-900 text-center text-sm mt-1 mr-1">
  197. <div>This site is protected by invisible reCAPTCHA and </div>
  198. <div>
  199. <span> the Google </span>
  200. <a href="https://policies.google.com/privacy" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Privacy Policy</a>
  201. <span> and </span>
  202. <a href="https://policies.google.com/terms" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Terms of Service</a> apply.
  203. </div>
  204. </div>
  205. }
  206. <ReCAPTCHA
  207. ref={recaptchaRef}
  208. size="invisible"
  209. sitekey={window?.appCfg?.reCaptchaSiteKey ?? 'SiteKeyNotFound'}
  210. />
  211. </div>
  212. <Persist name="feedback-form" isSessionStorage />
  213. </Form>
  214. </div>
  215. )
  216. }}
  217. </Formik>
  218. )
  219. }
  220. export default FormFeedback
  221. FormFeedback.propTypes = {
  222. setPopupTitle: PropTypes.func,
  223. setPopupContent: PropTypes.func,
  224. setPopupOpened: PropTypes.func,
  225. close: PropTypes.func,
  226. }