This is just a technology testing project based on Create React App and TailwindCSS
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }