This is just a technology testing project based on Create React App and TailwindCSS
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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