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.

243 lines
9.2KB

  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.mixed()
  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().required('Required').test('is-valid-support-type', 'Invalid support type', val => {
  78. const sb = val.filter(n => !support_types_values.includes(n))
  79. return 0 === sb.length
  80. }),
  81. }),
  82. message: Yup.string()
  83. .required('Required')
  84. .max(500, val => `Maximum ${val.max} symbols`),
  85. })
  86. const recaptchaRef = React.useRef();
  87. const onSubmitWithReCAPTCHA = async (values, { resetForm }) => {
  88. setPopupOpened(false)
  89. if (recaptchaRef.current && recaptchaRef.current.executeAsync) {
  90. let token
  91. try {
  92. await Promise.race([
  93. new Promise((_, reject) => setTimeout(() => {
  94. reject(new Error('timeout'))
  95. }, 5000)),
  96. recaptchaRef.current.executeAsync(),
  97. ])
  98. .then((v) => {
  99. token = v
  100. })
  101. } catch (error) {
  102. console.error('app error - catch in try around Promise.race', error)
  103. token = false
  104. }
  105. if (token) {
  106. values.recaptcha = token
  107. // /* // <-- Stub code begin!
  108. console.log('values', values)
  109. resetForm()
  110. resetForm() //api bug workaround - need to call reset twice
  111. setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
  112. setPopupOpened(true)
  113. // */ // <-- Stub code end!
  114. /* // <-- Example code begin!
  115. const res = await someApiCall(values)
  116. if ('success' === res.state) {
  117. resetForm()
  118. resetForm() //api bug workaround - need to call reset twice
  119. setFormState('success')
  120. return
  121. } else if ('error' === res.state) {
  122. setFormMessage(res.payload)
  123. } else {
  124. setFormMessage('Incorrect server response')
  125. }
  126. // */ // <-- Example code end!
  127. } else {
  128. console.error('app error - reCaptcha token is empty')
  129. alert('reCaptcha token is empty')
  130. }
  131. } else {
  132. console.error('app error - reCaptcha is not rendered')
  133. alert('reCaptcha is not rendered')
  134. }
  135. }
  136. return (
  137. <Formik
  138. initialValues={initialValues}
  139. validationSchema={validationSchema}
  140. onSubmit={onSubmitWithReCAPTCHA}
  141. >
  142. {formik => {
  143. return (
  144. <div className="overflow-y-auto max-h-screen-80">
  145. <Form className="feedback-form" autoComplete="off">
  146. <div className="p-3">
  147. <FormikControl
  148. control='string'
  149. name='user_name'
  150. label='Your name *'
  151. placeholder='Enter your name'
  152. autoFocus={true}
  153. />
  154. <FormikControl
  155. control='string'
  156. name='email'
  157. label='Email *'
  158. placeholder='Enter email for reply'
  159. />
  160. <FormikControl
  161. control='radio-group'
  162. name='section'
  163. label='Section *'
  164. options={sections}
  165. />
  166. <AnimatePresence>
  167. {'users' === formik.values.section &&
  168. <motion.div
  169. variants={supportTypesMotionVariants}
  170. initial="hidden"
  171. animate="visible"
  172. exit="hidden"
  173. >
  174. <FormikControl
  175. control='checkbox-group'
  176. name='support_type'
  177. label='Required types of support *'
  178. options={support_types}
  179. />
  180. </motion.div>
  181. }
  182. </AnimatePresence>
  183. <FormikControl
  184. control='text'
  185. name='message'
  186. label='Message *'
  187. placeholder='Enter your message'
  188. />
  189. </div>
  190. <div className="px-2 pt-4 pb-3 mb-0 bg-primary-200 border-t sm:rounded-b-lg">
  191. <ButtonPrimary type="submit" text="Send" disabled={!formik.isValid || !formik.dirty} />
  192. {(!formik.isValid || !formik.dirty)
  193. ? <div className="error text-center text-sm text-red-700 mt-1 mr-1">Please fill all necessary form fields</div>
  194. : <div className="text-gray-900 text-center text-sm mt-1 mr-1">
  195. <div>This site is protected by reCAPTCHA and the Google </div>
  196. <div>
  197. <a href="https://policies.google.com/privacy" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Privacy Policy</a>
  198. <span> and </span>
  199. <a href="https://policies.google.com/terms" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Terms of Service</a> apply.
  200. </div>
  201. </div>
  202. }
  203. <ReCAPTCHA
  204. ref={recaptchaRef}
  205. size="invisible"
  206. sitekey={window?.appCfg?.reCaptchaSiteKey ?? 'SiteKeyNotFound'}
  207. />
  208. </div>
  209. <Persist name="feedback-form" isSessionStorage />
  210. </Form>
  211. </div>
  212. )
  213. }}
  214. </Formik>
  215. )
  216. }
  217. export default FormFeedback
  218. FormFeedback.propTypes = {
  219. setPopupTitle: PropTypes.func,
  220. setPopupContent: PropTypes.func,
  221. setPopupOpened: PropTypes.func,
  222. close: PropTypes.func,
  223. }