瀏覽代碼

reCaptcha added to feedback form +other changes

master
ChiefRed 3 年之前
父節點
當前提交
fb6122e7d3
共有 23 個文件被更改,包括 7545 次插入103 次删除
  1. +17
    -14
      package.json
  2. +5
    -0
      public/index.html
  3. +8
    -0
      react-formik-tailwind-labwork.code-workspace
  4. +18
    -15
      src/App.jsx
  5. +7
    -6
      src/Popup.jsx
  6. +14
    -3
      src/ServicePolicy.jsx
  7. +27
    -0
      src/SubmitFormResult.jsx
  8. +6707
    -8
      src/assets/styles/.index.css
  9. +10
    -2
      src/assets/styles/index.tailwind.css
  10. +1
    -1
      src/components/FormikControls/FormikControlTypes/FormikControlCheckbox.jsx
  11. +39
    -0
      src/components/FormikControls/FormikControlTypes/FormikControlCheckboxGroup.jsx
  12. +39
    -0
      src/components/FormikControls/FormikControlTypes/FormikControlRadioGroup.jsx
  13. +29
    -0
      src/components/FormikControls/FormikControlTypes/FormikControlText.jsx
  14. +1
    -1
      src/components/FormikControls/FormikControlTypes/FormikControlUserConsent.jsx
  15. +13
    -12
      src/components/FormikControls/index.js
  16. +6
    -5
      src/components/controls/ButtonPrimary.jsx
  17. +6
    -5
      src/components/controls/ButtonSecondary.jsx
  18. +241
    -0
      src/components/forms/FormFeedback.jsx
  19. +19
    -13
      src/components/forms/FormLogin.jsx
  20. +83
    -0
      src/components/forms/FormPasswordRecovery.jsx
  21. +13
    -10
      src/components/forms/FormRegistration.jsx
  22. +33
    -3
      tailwind.js
  23. +209
    -5
      yarn.lock

+ 17
- 14
package.json 查看文件

@@ -4,15 +4,18 @@
"private": true,
"homepage": ".",
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"autoprefixer": "9.8.6",
"postcss-cli": "^8.0.0",
"prop-types": "^15.7.2",
"@tailwindcss/custom-forms": "^0.2.1",
"formik": "^2.1.7",
"formik-persist": "^1.1.0",
"framer-motion": "^2.7.7",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
"react-google-recaptcha": "^2.1.0",
"react-json-view": "^1.19.1",
"react-remove-scroll": "^2.4.0",
"tailwindcss": "^1.8.10",
"tailwindcss-filters": "^3.0.0",
"yup": "^0.29.3"
},
"scripts": {
"start": "yarn build:css && react-scripts start",
@@ -38,12 +41,12 @@
]
},
"devDependencies": {
"@tailwindcss/custom-forms": "^0.2.1",
"formik": "^2.1.7",
"formik-persist": "^1.1.0",
"framer-motion": "^2.7.7",
"tailwindcss": "^1.8.10",
"tailwindcss-filters": "^3.0.0",
"yup": "^0.29.3"
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"autoprefixer": "9.8.6",
"postcss-cli": "^8.0.0",
"prop-types": "^15.7.2",
"react-scripts": "3.4.3"
}
}

+ 5
- 0
public/index.html 查看文件

@@ -25,6 +25,11 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<script type="text/javascript">
window.appCfg = {
reCaptchaSiteKey: atob('Nkxmd3ROOFpBQUFBQUtNdHhQS0R6UEtlWUVkMWVJSlRKem9rWEtPaA=='),
}
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

+ 8
- 0
react-formik-tailwind-labwork.code-workspace 查看文件

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

+ 18
- 15
src/App.jsx 查看文件

@@ -5,33 +5,29 @@ import Popup from './Popup';

import { FormLogin } from './components/forms/FormLogin'
import { FormRegistration } from './components/forms/FormRegistration';
import { FormFeedback } from './components/forms/FormFeedback';

const App = () => {
const [popupTitle, setPopupTitle] = useState('')
const [popupContent, setPopupContent] = useState(<></>)
const [popupBack, setPopupBack] = useState(null)
const [popupOpened, setPopupOpened] = useState(false)

const close = () => {
if (popupBack) {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(popupBack)
setPopupOpened(true)
}, 200)
} else {
setPopupOpened(false)
}
setPopupOpened(false)
}

const doOpenLoginPopup = () => {
setPopupContent(<FormLogin setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<FormLogin setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}

const doOpenRegistrationPopup = () => {
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}

const doOpenFeedbackPopup = () => {
setPopupContent(<FormFeedback setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}

@@ -40,12 +36,19 @@ const App = () => {
<div className="w-full h-full min-h-screen bg-white text-gray-900 text-lg flex items-center">
<div className="max-w-lg w-full mx-auto py-8">
<div className="my-4">
<ButtonPrimary text="Login" action={doOpenLoginPopup} />
<ButtonPrimary text="Sign In" action={doOpenLoginPopup} />
</div>
<div className="my-4">
<ButtonPrimary text="Registration" action={doOpenRegistrationPopup} />
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis voluptates necessitatibus impedit laudantium, ea quam voluptatibus unde nulla voluptas neque suscipit temporibus ipsam ducimus eum inventore libero, ullam soluta minima adipisci omnis! Deleniti alias perspiciatis et? Accusamus aliquam eveniet voluptas vitae tenetur quas quae maxime necessitatibus mollitia molestias, commodi sunt quibusdam, cum, vero qui exercitationem iste beatae eaque dicta odit deleniti dolorem! Eius itaque quasi quibusdam tempora fugit laboriosam magni quod vel voluptas, officiis culpa numquam, molestias tempore. Sapiente exercitationem doloremque velit dolores excepturi asperiores, cumque minus earum enim voluptatibus quod veritatis non tempora nemo quo officiis expedita eveniet ratione atque! Illo ab accusamus vero, quas quisquam soluta. Voluptatum repudiandae asperiores laboriosam blanditiis dolorum quo, minus porro consequuntur eius nihil illum quaerat quod, laudantium omnis temporibus inventore voluptate, explicabo libero numquam beatae saepe? Repudiandae perferendis nostrum sit nam ad eaque expedita doloribus? Deserunt perspiciatis quo nulla dolor voluptatum temporibus, beatae iusto veritatis laboriosam reiciendis cupiditate ab quisquam. Distinctio neque, voluptate enim, aliquam dignissimos inventore perspiciatis tempore, quae officia quis minima necessitatibus at repellendus. Animi corrupti at modi quas et ipsam asperiores a ducimus consequuntur necessitatibus! Tempore, quisquam? Magnam harum ducimus cupiditate aspernatur, sit veritatis ipsa ratione maxime similique fuga corporis consequuntur obcaecati quas provident ut! Dignissimos labore qui amet in, accusantium minus suscipit numquam saepe nulla hic vero molestias mollitia autem, magni rem illo fugit doloremque est consectetur? Iure laborum nesciunt possimus quod animi temporibus perspiciatis modi aspernatur alias, maxime, sint dolores. Ab quaerat beatae dolorem itaque ex, similique dolore consectetur voluptatibus tenetur maiores eaque tempore quos ullam facilis illo nisi ipsum veniam maxime pariatur sint iste? Modi voluptas molestias inventore omnis repellendus? Veniam alias rerum voluptatum aut possimus labore, iste neque recusandae eveniet aspernatur dolor mollitia accusantium repudiandae molestias magni odit illum dolorem iure nesciunt cumque commodi voluptatibus sed nobis! Labore sint optio voluptatum, harum minima ad cum dolorem inventore sed qui eligendi asperiores nihil, quisquam veniam unde quaerat eveniet ipsam reprehenderit. Molestias sequi sapiente, aperiam iusto nostrum obcaecati accusamus tenetur eum excepturi cupiditate voluptatem dolor consequuntur, modi nesciunt quibusdam nam quae, illo sed animi debitis praesentium deserunt! Obcaecati, repellendus odit. Commodi, omnis cupiditate qui atque debitis repellendus eius ipsam deserunt cum pariatur, ut illum nisi aspernatur exercitationem odit consectetur aliquam nam dignissimos minus non. Nam possimus totam doloremque ipsam architecto enim corrupti officia iste debitis ab nemo, exercitationem facere laborum optio magni earum, temporibus, impedit ipsa perferendis! Ex!</p>
<div className="my-4">
<ButtonPrimary text="Feedback" action={doOpenFeedbackPopup} />
</div>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. </p>
<p>Delectus sequi, iste error dignissimos necessitatibus aliquam perspiciatis pariatur reprehenderit dolor maiores sed officiis modi, praesentium atque? Delectus amet optio tempora ipsam, molestiae unde quibusdam minus placeat dolorem exercitationem enim alias?</p>
<p>Porro tempore natus minus ex voluptas, hic culpa iure vero quod laudantium quas enim pariatur ullam commodi! Totam deleniti nesciunt earum, nam modi aspernatur rem harum pariatur, voluptatibus, adipisci accusamus sequi perferendis corrupti explicabo excepturi commodi soluta necessitatibus! Officiis similique nemo architecto odit debitis cupiditate reprehenderit ex vel atque beatae blanditiis dolores sequi unde quisquam molestias, eum sit aut. Soluta hic provident autem eius a aliquid neque in, distinctio eveniet ullam sequi pariatur deleniti perferendis magni debitis minus quod!</p>
<p>Facere ex error molestiae maxime ullam dolor blanditiis dolores fuga ipsam vero distinctio, ipsum delectus harum optio quo repudiandae unde beatae rerum laborum cumque, quisquam quia quod?</p>
<p>Numquam praesentium ex recusandae quia tenetur, quod, illum officiis magni est alias consequatur maiores accusamus ipsam provident sequi dolores excepturi exercitationem vitae aspernatur perferendis porro sit accusantium aut? Tempora nesciunt, autem dignissimos sit ratione dolores odit amet laborum nihil corporis id nobis tempore! Quam rerum repudiandae expedita voluptate error dolorum a. Obcaecati fugiat magnam earum?</p>
</div>
</div>
<Popup opened={popupOpened} title={popupTitle} close={close}>{popupContent}</Popup>

+ 7
- 6
src/Popup.jsx 查看文件

@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { motion, AnimatePresence } from "framer-motion"
import { RemoveScroll } from 'react-remove-scroll'

export const Popup = (props) => {
const { opened, title, close, children } = props
@@ -25,8 +26,6 @@ export const Popup = (props) => {
},
}

//document.body.style.overflow='hidden';
// Если надо закрывать по клику за попапом, то в первый див надо добавить onClick={close}
return <>
<AnimatePresence>
{opened &&
@@ -40,12 +39,14 @@ export const Popup = (props) => {
animate="visible"
exit="hidden"
>
<div tabIndex="0" aria-label={title} aria-modal className="ma-0 bg-gray-200 shadow-lg border-white border-t-2 border-b-2 sm:border-2 sm:mx-4 sm:rounded-lg transition-opacity duration-300" onClick={e => e.stopPropagation()}>
<div tabIndex="0" aria-label={title} aria-modal className="ma-0 bg-gray-200 shadow-lg border-white border-t-2 border-b-2 sm:border-2 sm:mx-4 sm:rounded-lg focus:outline-none" onClick={e => e.stopPropagation()}>
<div className="px-3 h-12 bg-primary-800 text-white border-b sm:rounded-t-lg border-white font-semibold relative flex items-center">
<div className="w-full text-xl">{title}</div>
<button type="button" aria-label="Close modal" className="absolute top-0 right-0 w-12 h-12 pb-1 text-3xl leading-none cursor-pointer flex items-center justify-center" onClick={close}>&times;</button>
<div className="w-full text-xl truncate pr-6">{title}</div>
<button type="button" aria-label="Close modal" className="absolute top-0 right-0 w-12 h-12 pb-1 text-3xl leading-none cursor-pointer flex items-center justify-center transform origin-center hover:scale-125 active:scale-110 focus:outline-none" onClick={close}>&times;</button>
</div>
{children}
<RemoveScroll removeScrollBar={false}>
{children}
</RemoveScroll>
</div>
</motion.div>
</div>

+ 14
- 3
src/ServicePolicy.jsx 查看文件

@@ -1,17 +1,26 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import ButtonSecondary from './components/controls/ButtonSecondary'
import { FormRegistration } from './components/forms/FormRegistration'

export function ServicePolicy(props) {
const { setPopupTitle, close } = props
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('Service Policy') }
})

const goRegistration = () => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}

return (
<>
<div className="overflow-y-auto max-h-screen-75 p-3">
<div className="overflow-y-auto max-h-screen-66 p-3">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. </p>
<p>Delectus sequi, iste error dignissimos necessitatibus aliquam perspiciatis pariatur reprehenderit dolor maiores sed officiis modi, praesentium atque? Delectus amet optio tempora ipsam, molestiae unde quibusdam minus placeat dolorem exercitationem enim alias?</p>
<p>Porro tempore natus minus ex voluptas, hic culpa iure vero quod laudantium quas enim pariatur ullam commodi! Totam deleniti nesciunt earum, nam modi aspernatur rem harum pariatur, voluptatibus, adipisci accusamus sequi perferendis corrupti explicabo excepturi commodi soluta necessitatibus! Officiis similique nemo architecto odit debitis cupiditate reprehenderit ex vel atque beatae blanditiis dolores sequi unde quisquam molestias, eum sit aut. Soluta hic provident autem eius a aliquid neque in, distinctio eveniet ullam sequi pariatur deleniti perferendis magni debitis minus quod!</p>
@@ -19,7 +28,7 @@ export function ServicePolicy(props) {
<p>Numquam praesentium ex recusandae quia tenetur, quod, illum officiis magni est alias consequatur maiores accusamus ipsam provident sequi dolores excepturi exercitationem vitae aspernatur perferendis porro sit accusantium aut? Tempora nesciunt, autem dignissimos sit ratione dolores odit amet laborum nihil corporis id nobis tempore! Quam rerum repudiandae expedita voluptate error dolorum a. Obcaecati fugiat magnam earum?</p>
</div>
<div className="p-2 mb-0 bg-primary-200 border-t rounded-b-lg">
<ButtonSecondary type="button" text="Go back" action={close} />
<ButtonSecondary type="button" text="Go to Registration" action={goRegistration} />
</div>
</>
)
@@ -27,6 +36,8 @@ export function ServicePolicy(props) {

ServicePolicy.propTypes = {
setPopupTitle: PropTypes.func,
setPopupContent: PropTypes.func,
setPopupOpened: PropTypes.func,
close: PropTypes.func,
}


+ 27
- 0
src/SubmitFormResult.jsx 查看文件

@@ -0,0 +1,27 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import ReactJson from 'react-json-view'

export function SubmitFormResult(props) {
const { setPopupTitle, data } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('Data submitted from the form') }
})

return (
<>
<div className="overflow-y-auto max-h-screen-75 p-3 text-xl">
<ReactJson name="formData" src={data} enableClipboard={false} />
</div>
</>
)
}

SubmitFormResult.propTypes = {
setPopupTitle: PropTypes.func,
data: PropTypes.object,
}

export default SubmitFormResult


+ 6707
- 8
src/assets/styles/.index.css
文件差異過大導致無法顯示
查看文件


+ 10
- 2
src/assets/styles/index.tailwind.css 查看文件

@@ -1,11 +1,10 @@
/* purgecss start ignore */

@tailwind base;
@tailwind components;
/* purgecss end ignore */

@tailwind utilities;


/***** FONTS *****/

@font-face {
@@ -43,3 +42,12 @@
font-style: normal;
font-display: swap;
}

p {
text-indent: 1rem;
margin: .3rem 0 .5rem 0;
}

.grecaptcha-badge {
display: none !important;
}

+ 1
- 1
src/components/FormikControls/FormikControlTypes/FormikControlCheckbox.jsx 查看文件

@@ -11,7 +11,7 @@ function FormikControlCheckbox(props) {
{({ field }) => {
const htmlId = newId()
return <>
<label htmlFor={htmlId} className="inline-flex items-center py-1">
<label htmlFor={htmlId} className="flex items-center p-1 rounded-md cursor-pointer hover:bg-gray-100">
<input type="checkbox" id={htmlId} name={name} {...rest} className="form-checkbox text-primary-800" {...field} checked={field.value} />
<span className="ml-4 text-base sm:text-lg ">{label}</span>
</label>

+ 39
- 0
src/components/FormikControls/FormikControlTypes/FormikControlCheckboxGroup.jsx 查看文件

@@ -0,0 +1,39 @@
import React from 'react'
import { Field, ErrorMessage } from 'formik'
import FormikControlErrorMessage from '../FormikControlErrorMessage'
import newId from '../../../utils/newId';

function FormikControlCheckboxGroup(props) {
const { name, label, options, ...rest } = props
return (
<div className="form-control mt-6 mb-4">
<label className="form-control block text-lg text-gray-800">{label}</label>
<Field name={name}>
{({ field }) => {
return options.map(option => {
const htmlId = newId()
return (
<React.Fragment key={option.label}>
<label htmlFor={htmlId} className="text-gray-600 flex items-center cursor-pointer hover:bg-gray-100">
<input
type='checkbox'
id={htmlId}
{...field}
{...rest}
value={option.value}
checked={field.value.includes(option.value)}
className='form-checkbox flex-shrink-0 m-2 text-primary-700'
/>
<span>{option.label}</span>
</label>
</React.Fragment>
)
})
}}
</Field>
<ErrorMessage component={FormikControlErrorMessage} name={name} />
</div>
)
}

export default FormikControlCheckboxGroup

+ 39
- 0
src/components/FormikControls/FormikControlTypes/FormikControlRadioGroup.jsx 查看文件

@@ -0,0 +1,39 @@
import React from 'react'
import { Field, ErrorMessage } from 'formik'
import FormikControlErrorMessage from '../FormikControlErrorMessage'
import newId from '../../../utils/newId';

function FormikControlRadioGroup(props) {
const { name, label, options, ...rest } = props
return (
<div className="form-control mt-6 mb-4">
<label className="form-control block text-lg text-gray-800">{label}</label>
<Field name={name}>
{({ field }) => {
return options.map(option => {
const htmlId = newId()
return (
<React.Fragment key={option.label}>
<label htmlFor={htmlId} className="text-gray-600 flex items-center cursor-pointer hover:bg-gray-100">
<input
type='radio'
id={htmlId}
{...field}
{...rest}
value={option.value}
checked={field.value.includes(option.value)}
className='form-radio flex-shrink-0 m-2 text-primary-700'
/>
<span>{option.label}</span>
</label>
</React.Fragment>
)
})
}}
</Field>
<ErrorMessage component={FormikControlErrorMessage} name={name} />
</div>
)
}

export default FormikControlRadioGroup

+ 29
- 0
src/components/FormikControls/FormikControlTypes/FormikControlText.jsx 查看文件

@@ -0,0 +1,29 @@
import React from 'react'
import { Field, ErrorMessage } from 'formik'
import FormikControlErrorMessage from '../FormikControlErrorMessage'
import newId from '../../../utils/newId';

function FormikControlText(props) {
const { label, name, ...rest } = props
const htmlId = newId()
return (
<div className="mt-4 mb-2">
<label htmlFor={htmlId} className="form-control block text-lg text-gray-800 py-1">
<span className="text-gray-700 ml-1">{label}</span>
</label>
<Field
as="textarea"
label={label}
title={label}
id={htmlId}
name={name}
aria-describedby={name + 'Error'}
{...rest}
className="form-input mt-1 block w-full min-h-screen-10 max-h-screen-20 text-left tracking-widest"
/>
<ErrorMessage component={FormikControlErrorMessage} name={name} label={label} id={name + 'Error'} />
</div>
)
}

export default FormikControlText

+ 1
- 1
src/components/FormikControls/FormikControlTypes/FormikControlUserConsent.jsx 查看文件

@@ -21,7 +21,7 @@ function FormikControlUserConsent(props) {
<Field name={name}>
{({ field }) => {
return <>
<label htmlFor={htmlId} className="inline-flex items-center py-1">
<label htmlFor={htmlId} className="flex items-center p-1 rounded-md cursor-pointer hover:bg-gray-100">
<input
type="checkbox"
id={htmlId}

+ 13
- 12
src/components/FormikControls/index.js 查看文件

@@ -1,8 +1,11 @@
import React from 'react'
import FormikControlString from './FormikControlTypes/FormikControlString'
import FormikControlText from './FormikControlTypes/FormikControlText';
import FormikControlPassword from './FormikControlTypes/FormikControlPassword'
import FormikControlUserConsent from './FormikControlTypes/FormikControlUserConsent'
import FormikControlCheckbox from './FormikControlTypes/FormikControlCheckbox'
import FormikControlCheckboxGroup from './FormikControlTypes/FormikControlCheckboxGroup';
import FormikControlRadioGroup from './FormikControlTypes/FormikControlRadioGroup';
import FormikControlUserConsent from './FormikControlTypes/FormikControlUserConsent'


export function FormikControl(props) {
@@ -12,20 +15,18 @@ export function FormikControl(props) {
return <FormikControlString {...rest} />
case 'password':
return <FormikControlPassword {...rest} />
// case 'textarea':
// return <FormikInputTextarea {...rest} />
case 'text':
return <FormikControlText {...rest} />
// case 'select':
// return <FormikInputSelect {...rest} />
// case 'radio':
// return <FormikInputRadioGroup {...rest} />
// case 'checkbox':
// return <FormikInputCheckbox {...rest} />
// case 'phone':
// return <FormikInputPhone {...rest} />
case 'userconsent':
return <FormikControlUserConsent {...rest} />
// return <FormikControlSelect {...rest} />
case 'radio-group':
return <FormikControlRadioGroup {...rest} />
case 'checkbox':
return <FormikControlCheckbox {...rest} />
case 'checkbox-group':
return <FormikControlCheckboxGroup {...rest} />
case 'userconsent':
return <FormikControlUserConsent {...rest} />
default:
return null
}

+ 6
- 5
src/components/controls/ButtonPrimary.jsx 查看文件

@@ -10,32 +10,33 @@ function PrimaryButton(props) {
flex
relative
w-full
p-3
py-3
border-0
rounded-md
border-primary-800
bg-primary-800
text-white
truncate
cursor-pointer
transform
shadow-md
hover:bg-primary-500
focus:bg-primary-500
focus:outline-none
active:shadow-inner
active:bg-primary-800
active:transform
active:translate-y-px
active:scale-98
disabled:bg-primary-800
disabled:opacity-50
disabled:shadow-md
disabled:translate-y-0
disabled:scale-100
disabled:cursor-not-allowed
"
onClick={action}
disabled={disabled}
aria-disabled={disabled}
>
<div className="sm:text-lg flex-grow text-center px-6">{text||'Primary Button'}</div>
<div className="sm:text-lg flex-grow text-center px-2">{text||'Primary Button'}</div>
</button>
{disabled && <div tabIndex="0" className="sr-only">Disabled button: {text||'Primary Button'}. Please fill all necessary fields.</div>}
</>

+ 6
- 5
src/components/controls/ButtonSecondary.jsx 查看文件

@@ -10,13 +10,15 @@ function ButtonSecondary(props) {
flex
relative
w-full
p-3
py-3
border-2
rounded-md
border-primary-800
bg-white
text-primary-800
truncate
cursor-pointer
transform
shadow-md
hover:bg-primary-500
hover:text-white
@@ -24,18 +26,17 @@ function ButtonSecondary(props) {
focus:outline-none
active:shadow-inner
active:bg-primary-500
active:transform
active:translate-y-px
active:scale-98
disabled:bg-white
disabled:opacity-50
disabled:shadow-md
disabled:translate-y-0
disabled:scale-100
disabled:cursor-not-allowed
"
onClick={action}
disabled={disabled}
>
<div className="sm:text-lg flex-grow text-center px-6">{text||'primary Button'}</div>
<div className="sm:text-lg flex-grow text-center px-2">{text||'primary Button'}</div>
</button>
</>
}

+ 241
- 0
src/components/forms/FormFeedback.jsx 查看文件

@@ -0,0 +1,241 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { motion, AnimatePresence } from "framer-motion"
import { Formik, Form } from 'formik'
import { Persist } from 'formik-persist'
import * as Yup from 'yup'
import ReCAPTCHA from "react-google-recaptcha";
import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import { SubmitFormResult } from '../../SubmitFormResult';

export const FormFeedback = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('Some feedback form example') }
})

const sections = [
{
label: 'User support',
value: 'users'
},
{
label: 'Partnership',
value: 'partners'
},
]

const support_types = [
{
label: 'Consulting on product',
value: 'product'
},
{
label: 'Technical support',
value: 'technical'
},
{
label: 'Legal support',
value: 'legal'
},
]

const support_types_values = support_types.map(val => val.value)

const supportTypesMotionVariants = {
hidden: {
opacity: 0,
scaleY: 0,
transition: {
type: "linear",
duration: .2
}
},
visible: {
opacity: 1,
scaleY: 1,
transition: {
type: "linear",
duration: .2
}
},
}

const initialValues = {
user_name: '',
email: '',
section: '',
support_type: [],
message: '',
}

const validationSchema = Yup.object({
user_name: Yup.string()
.required('Required'),
email: Yup.string()
.required('Required')
.email('Must be a valid email address'),
section: Yup.mixed()
.required('Required')
.oneOf(['users', 'partners'], 'Invalid section'),
support_type: Yup.array(Yup.string()).ensure().when('section', {
is: 'users',
then: Yup.array().required('Required').test('is-valid-support-type', 'Invalid support type', val => {
const sb = val.filter(n => !support_types_values.includes(n))
return 0 === sb.length
}),
}),
message: Yup.string()
.required('Required')
.max(500, val => `Maximum ${val.max} symbols`),
})

const recaptchaRef = React.useRef();

const onSubmitWithReCAPTCHA = async (values, { resetForm }) => {
setPopupOpened(false)
if (recaptchaRef.current && recaptchaRef.current.executeAsync) {

let token
try {
await Promise.race([
new Promise((_, reject) => setTimeout(() => {
reject(new Error('timeout'))
}, 5000)),
recaptchaRef.current.executeAsync(),
])
.then((v) => {
token = v
})
} catch (error) {
console.error('app error - catch in try around Promise.race', error)
token = false
}

if (token) {
values.recaptcha = token

// /* // <-- Stub code begin!
console.log('values', values)
resetForm()
resetForm() //api bug workaround - need to call reset twice
setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
setPopupOpened(true)
// */ // <-- Stub code end!

/* // <-- Example code begin!
const res = await someApiCall(values)
if ('success' === res.state) {
resetForm()
resetForm() //api bug workaround - need to call reset twice
setFormState('success')
return
} else if ('error' === res.state) {
setFormMessage(res.payload)
} else {
setFormMessage('Incorrect server response')
}
// */ // <-- Example code end!

} else {
console.error('app error - reCaptcha token is empty')
alert('reCaptcha token is empty')
}
} else {
console.error('app error - reCaptcha is not rendered')
alert('reCaptcha is not rendered')
}
}

return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmitWithReCAPTCHA}
>
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form autoComplete="off">
<div className="p-3">
<FormikControl
control='string'
name='user_name'
label='Your name *'
placeholder='Enter your name'
autoFocus={true}
/>
<FormikControl
control='string'
name='email'
label='Email *'
placeholder='Enter email for reply'
/>
<FormikControl
control='radio-group'
name='section'
label='Section *'
options={sections}
/>
<AnimatePresence>
{'users' === formik.values.section &&
<motion.div
variants={supportTypesMotionVariants}
initial="hidden"
animate="visible"
exit="hidden"
>
<FormikControl
control='checkbox-group'
name='support_type'
label='Required types of support *'
options={support_types}
/>
</motion.div>
}
</AnimatePresence>
<FormikControl
control='text'
name='message'
label='Message *'
placeholder='Enter your message'
/>

</div>
<div className="px-2 pt-4 pb-3 mb-0 bg-primary-200 border-t sm:rounded-b-lg">
<ButtonPrimary type="submit" text="Send" disabled={!formik.isValid || !formik.dirty} />
{(!formik.isValid || !formik.dirty)
? <div className="error text-center text-sm text-red-700 mt-1 mr-1">Please fill all necessary form fields</div>
: <div className="text-gray-900 text-center text-sm mt-1 mr-1">
<div>This site is protected by reCAPTCHA and the Google</div>
<div>
<a href="https://policies.google.com/privacy" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Privacy Policy</a> and
<a href="https://policies.google.com/terms" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Terms of Service</a> apply.
</div>
</div>
}
<ReCAPTCHA
ref={recaptchaRef}
size="invisible"
sitekey={window?.appCfg?.reCaptchaSiteKey ?? 'SiteKeyNotFound'}
/>
</div>
<Persist name="feedback-form" isSessionStorage />
</Form>
</div>
)
}}
</Formik>
)
}

export default FormFeedback

FormFeedback.propTypes = {
setPopupTitle: PropTypes.func,
setPopupContent: PropTypes.func,
setPopupOpened: PropTypes.func,
close: PropTypes.func,
}

+ 19
- 13
src/components/forms/FormLogin.jsx 查看文件

@@ -8,43 +8,50 @@ import ButtonPrimary from '../controls/ButtonPrimary'
import ButtonSecondary from '../controls/ButtonSecondary'
import Link from '../controls/Link'
import { FormRegistration } from './FormRegistration'
import { FormPasswordRecovery } from './FormPasswordRecovery';
import { SubmitFormResult } from '../../SubmitFormResult';

export const FormLogin = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, setPopupBack, close } = props
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('Enter into restricted area') }
if (setPopupTitle) { setPopupTitle('Sign in the restricted area') }
})

const initialValues = {
login: '',
email: '',
password: '',
remember_me: false,
}

const validationSchema = Yup.object({
email: Yup.string()
.required('Required'),
.required('Required')
.email('Must be a valid email address'),
password: Yup.string()
.required('Required'),
})

const onSubmit = async values => {
console.log('Form data', values)
const onSubmit = values => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
setPopupOpened(true)
}, 200);
}

const goRegistration = () => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}

const goRestorePassword = () => {
const goPasswordRecovery = () => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<FormPasswordRecovery setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}
@@ -57,7 +64,7 @@ export const FormLogin = (props) => {
>
{formik => {
return (
<div className="overflow-y-auto max-h-screen-75">
<div className="overflow-y-auto max-h-screen-80">
<Form>
<div className="p-3">
<FormikControl
@@ -80,11 +87,11 @@ export const FormLogin = (props) => {
label='Remember me'
/>
</div>
<Link action={goRestorePassword} className="block px-2 mt-6 mb-4 leading-8 text-lg">Forgot password?</Link>
<Link action={goPasswordRecovery} className="block px-2 mt-6 mb-4 leading-8 text-lg">Forgot password?</Link>
</div>
</div>
<div className="px-2 pt-4 pb-1 mb-0 bg-primary-200 border-t sm:rounded-b-lg">
<ButtonPrimary type="submit" text="Enter" disabled={!formik.isValid || !formik.dirty} />
<ButtonPrimary type="submit" text="Sign In" disabled={!formik.isValid || !formik.dirty} />
<div className="my-3">
<ButtonSecondary type="button" text="Have no account? - Create" action={goRegistration} />
</div>
@@ -102,6 +109,5 @@ FormLogin.propTypes = {
setPopupTitle: PropTypes.func,
setPopupContent: PropTypes.func,
setPopupOpened: PropTypes.func,
setPopupBack: PropTypes.func,
close: PropTypes.func,
}

+ 83
- 0
src/components/forms/FormPasswordRecovery.jsx 查看文件

@@ -0,0 +1,83 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { Formik, Form } from 'formik'
import { Persist } from 'formik-persist'
import * as Yup from 'yup'
import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import ButtonSecondary from '../controls/ButtonSecondary'
import { FormLogin } from './FormLogin'
import { SubmitFormResult } from '../../SubmitFormResult';

export const FormPasswordRecovery = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('Forgotten password recovery') }
})

const initialValues = {
email: '',
}

const validationSchema = Yup.object({
email: Yup.string()
.required('Required')
.email('Must be a valid email address'),
})

const onSubmit = values => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
setPopupOpened(true)
}, 200);
}

const goLogin = () => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<FormLogin setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}

return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form>
<div className="p-3">
<FormikControl
control='string'
name='email'
label='Email'
placeholder='Enter email'
/>
</div>
<div className="px-2 pt-4 pb-1 mb-0 bg-primary-200 border-t sm:rounded-b-lg">
<ButtonPrimary type="submit" text="Recover" disabled={!formik.isValid || !formik.dirty} />
<div className="my-3">
<ButtonSecondary type="button" text="Go to Sign In" action={goLogin} />
</div>
</div>
<Persist name="password-recovery-form" isSessionStorage />
</Form>
</div>
)
}}
</Formik>
)
}

FormPasswordRecovery.propTypes = {
setPopupTitle: PropTypes.func,
setPopupContent: PropTypes.func,
setPopupOpened: PropTypes.func,
close: PropTypes.func,
}

+ 13
- 10
src/components/forms/FormRegistration.jsx 查看文件

@@ -7,10 +7,11 @@ import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import ButtonSecondary from '../controls/ButtonSecondary'
import { FormLogin } from './FormLogin'
import ServicePolicy from '../../ServicePolicy'
import { ServicePolicy } from '../../ServicePolicy'
import { SubmitFormResult } from '../../SubmitFormResult';

export const FormRegistration = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, setPopupBack, close } = props
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props

useEffect(() => {
if (setPopupTitle) { setPopupTitle('New user registration') }
@@ -29,7 +30,7 @@ export const FormRegistration = (props) => {
.email('Must be a valid email address'),
password: Yup.string()
.required('Required')
.min(8, 'Minimum 8 symbols'),
.min(8, val => `Minimum ${val.min} symbols`),
password_confirm: Yup.string()
.required('Required')
.oneOf([Yup.ref('password'), null], 'Not matching with password'),
@@ -37,23 +38,26 @@ export const FormRegistration = (props) => {
.oneOf([true], 'Required'),
})

const onSubmit = async values => {
console.log('Form data', values)
const onSubmit = values => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<SubmitFormResult setPopupTitle={setPopupTitle} data={values} />)
setPopupOpened(true)
}, 200);
}

const goLogin = () => {
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<FormLogin setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<FormLogin setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}

const goServicePolicy = () => {
setPopupBack(<FormRegistration setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupOpened(false)
setTimeout(() => {
setPopupContent(<ServicePolicy setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} setPopupBack={setPopupBack} close={close} />)
setPopupContent(<ServicePolicy setPopupTitle={setPopupTitle} setPopupContent={setPopupContent} setPopupOpened={setPopupOpened} close={close} />)
setPopupOpened(true)
}, 200);
}
@@ -66,7 +70,7 @@ export const FormRegistration = (props) => {
>
{formik => {
return (
<div className="overflow-y-auto max-h-screen-75">
<div className="overflow-y-auto max-h-screen-80">
<Form>
<div className="p-3">
<FormikControl
@@ -125,6 +129,5 @@ FormRegistration.propTypes = {
setPopupTitle: PropTypes.func,
setPopupContent: PropTypes.func,
setPopupOpened: PropTypes.func,
setPopupBack: PropTypes.func,
close: PropTypes.func,
}

+ 33
- 3
tailwind.js 查看文件

@@ -161,6 +161,10 @@ module.exports = {
},
spacing: {
px: '1px',
'2px': '2px',
'3px': '3px',
'4px': '4px',
'5px': '5px',
'0': '0',
'1': '0.25rem',
'2': '0.5rem',
@@ -362,9 +366,19 @@ module.exports = {
maxHeight: {
full: '100%',
screen: '100vh',
'screen-5': '5vh',
'screen-10': '10vh',
'screen-15': '15vh',
'screen-20': '20vh',
'screen-25': '25vh',
'screen-33': '33vh',
'screen-50': '50vh',
'screen-66': '66vh',
'screen-75': '75vh',
'screen-80': '80vh',
'screen-85': '85vh',
'screen-90': '90vh',
'screen-95': '95vh',
},
maxWidth: (theme, { breakpoints }) => ({
none: 'none',
@@ -385,6 +399,16 @@ module.exports = {
'0': '0',
full: '100%',
screen: '100vh',
'screen-5': '5vh',
'screen-10': '10vh',
'screen-15': '15vh',
'screen-20': '20vh',
'screen-25': '25vh',
'screen-33': '33vh',
'screen-50': '50vh',
'screen-66': '66vh',
'screen-75': '75vh',
'screen-80': '80vh',
},
minWidth: {
'0': '0',
@@ -601,7 +625,9 @@ module.exports = {
'75': '.75',
'90': '.9',
'95': '.95',
'98': '.98',
'100': '1',
'102': '1.02',
'105': '1.05',
'110': '1.1',
'125': '1.25',
@@ -741,8 +767,8 @@ module.exports = {
},
checkbox: {
borderWidth: theme('borderWidth.2'),
width: theme('spacing.6'),
height: theme('spacing.6'),
width: theme('spacing.5'),
height: theme('spacing.5'),
'&:focus': {
boxShadow: theme('boxShadow.outline'),
outline: 'none',
@@ -751,6 +777,10 @@ module.exports = {
},
radio: {
iconColor: theme('colors.primary.800'),
borderWidth: theme('borderWidth.2'),
borderColor: theme('colors.gray.500'),
width: theme('spacing.5'),
height: theme('spacing.5'),
'&:focus': {
boxShadow: theme('boxShadow.outline'),
outline: 'none',
@@ -872,7 +902,7 @@ module.exports = {
gridRowEnd: ['responsive'],
transform: ['responsive'],
transformOrigin: ['responsive'],
scale: ['responsive', 'hover', 'focus'],
scale: ['responsive', 'hover', 'focus', 'active', 'disabled'],
rotate: ['responsive', 'hover', 'focus'],
translate: ['responsive', 'hover', 'focus', 'active', 'disabled'],
skew: ['responsive', 'hover', 'focus'],

+ 209
- 5
yarn.lock 查看文件

@@ -2221,7 +2221,7 @@ arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=

asap@~2.0.6:
asap@~2.0.3, asap@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -2505,6 +2505,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=

base16@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=

base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
@@ -3329,6 +3334,11 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==

core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=

core-js@^2.4.0:
version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
@@ -3831,6 +3841,11 @@ detect-newline@^2.1.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=

detect-node-es@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.0.0.tgz#c0318b9e539a5256ca780dd9575c9345af05b8ed"
integrity sha512-S4AHriUkTX9FoFvL4G8hXDcx6t3gp2HpfCza3Q0v6S78gul2hKWifLQbeW+ZF89+hSm2ZIc/uF3J97ZgytgTRg==

detect-node@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
@@ -4095,6 +4110,13 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=

encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"

end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -4666,6 +4688,26 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"

fbemitter@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
integrity sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=
dependencies:
fbjs "^0.8.4"

fbjs@^0.8.0, fbjs@^0.8.4:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"

figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@@ -4817,6 +4859,14 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"

flux@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
integrity sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=
dependencies:
fbemitter "^2.0.0"
fbjs "^0.8.0"

fn-name@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
@@ -5033,6 +5083,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==

get-nonce@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==

get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -5484,6 +5539,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"

iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"

icss-utils@^4.0.0, icss-utils@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
@@ -5946,7 +6008,7 @@ is-root@2.1.0:
resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c"
integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==

is-stream@^1.1.0:
is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -6012,6 +6074,14 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=

isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"

isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -6809,6 +6879,11 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=

lodash.curry@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=

lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -6819,6 +6894,11 @@ lodash.difference@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=

lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=

lodash.forown@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.forown/-/lodash.forown-4.4.0.tgz#85115cf04f73ef966eced52511d3893cc46683af"
@@ -7322,6 +7402,14 @@ node-emoji@^1.8.1:
dependencies:
lodash.toarray "^4.4.0"

node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"

node-forge@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
@@ -8878,6 +8966,13 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=

promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"

promise@^8.0.3:
version "8.1.0"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
@@ -8893,7 +8988,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.4"

prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -8977,6 +9072,11 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==

pure-color@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=

purgecss@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3"
@@ -9074,6 +9174,24 @@ react-app-polyfill@^1.0.6:
regenerator-runtime "^0.13.3"
whatwg-fetch "^3.0.0"

react-async-script@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.2.0.tgz#ab9412a26f0b83f5e2e00de1d2befc9400834b21"
integrity sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==
dependencies:
hoist-non-react-statics "^3.3.0"
prop-types "^15.5.0"

react-base16-styling@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"

react-dev-utils@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19"
@@ -9124,11 +9242,53 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==

react-google-recaptcha@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz#9f6f4954ce49c1dedabc2c532347321d892d0a16"
integrity sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==
dependencies:
prop-types "^15.5.0"
react-async-script "^1.1.1"

react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==

react-json-view@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
integrity sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==
dependencies:
flux "^3.1.3"
react-base16-styling "^0.6.0"
react-lifecycles-compat "^3.0.4"
react-textarea-autosize "^6.1.0"

react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==

react-remove-scroll-bar@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.1.0.tgz#edafe9b42a42c0dad9bdd10712772a1f9a39d7b9"
integrity sha512-5X5Y5YIPjIPrAoMJxf6Pfa7RLNGCgwZ95TdnVPgPuMftRfO8DaC7F4KP1b5eiO8hHbe7u+wZNDbYN5WUTpv7+g==
dependencies:
react-style-singleton "^2.1.0"
tslib "^1.0.0"

react-remove-scroll@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.0.tgz#190c16eb508c5927595935499e8f5dd9ab0e75cf"
integrity sha512-BZIO3GaEs0Or1OhA5C//n1ibUP1HdjJmqUVUsOCMxwoIpaCocbB9TFKwHOkBa/nyYy3slirqXeiPYGwdSDiseA==
dependencies:
react-remove-scroll-bar "^2.1.0"
react-style-singleton "^2.1.0"
tslib "^1.0.0"
use-callback-ref "^1.2.3"
use-sidecar "^1.0.1"

react-scripts@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51"
@@ -9189,6 +9349,22 @@ react-scripts@3.4.3:
optionalDependencies:
fsevents "2.1.2"

react-style-singleton@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.0.tgz#7396885332e9729957f9df51f08cadbfc164e1c4"
integrity sha512-DH4ED+YABC1dhvSDYGGreAHmfuTXj6+ezT3CmHoqIEfxNgEYfIMoOtmbRp42JsUst3IPqBTDL+8r4TF7EWhIHw==
dependencies:
get-nonce "^1.0.0"
invariant "^2.2.4"
tslib "^1.0.0"

react-textarea-autosize@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
integrity sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==
dependencies:
prop-types "^15.6.0"

react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
@@ -9660,7 +9836,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"

"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -9846,7 +10022,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"

setimmediate@^1.0.4:
setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -10692,6 +10868,11 @@ ts-pnp@1.1.6, ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a"
integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==

tslib@^1.0.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
@@ -10761,6 +10942,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=

ua-parser-js@^0.7.18:
version "0.7.22"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==

unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -10888,6 +11074,19 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"

use-callback-ref@^1.2.3:
version "1.2.4"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c"
integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==

use-sidecar@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.3.tgz#17a4e567d4830c0c0ee100040e85a7fe68611e0f"
integrity sha512-ygJwGUBeQfWgDls7uTrlEDzJUUR67L8Rm14v/KfFtYCdHhtjHZx1Krb3DIQl3/Q5dJGfXLEQ02RY8BdNBv87SQ==
dependencies:
detect-node-es "^1.0.0"
tslib "^1.9.3"

use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -11164,6 +11363,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"

whatwg-fetch@>=0.10.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3"
integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==

whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"

Loading…
取消
儲存