The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .dockerignore
├── .editorconfig
├── .github
    ├── FUNDING.yml
    ├── ISSUE_TEMPLATE
    │   ├── 1-bug_report.yml
    │   └── config.yml
    └── workflows
    │   └── publish-docker-image.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── assets
    └── images
    │   ├── logo.svg
    │   └── screenshot.png
├── package.json
├── packages
    ├── answer-utils
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── package.json
    │   ├── src
    │   │   ├── answer-parser.ts
    │   │   ├── answer-to-api-object.ts
    │   │   ├── answer-to-html.ts
    │   │   ├── answer-to-json.ts
    │   │   ├── answer-to-plain.ts
    │   │   ├── apply-logic-to-fields.ts
    │   │   ├── calculate-action.ts
    │   │   ├── consts.ts
    │   │   ├── field-values-to-answer.ts
    │   │   ├── fields-to-validate-rules.ts
    │   │   ├── flatten-fields.ts
    │   │   ├── helper.ts
    │   │   ├── hidden-fields-to-html.ts
    │   │   ├── html-utils.ts
    │   │   ├── index.ts
    │   │   ├── validate-condition.ts
    │   │   ├── validate-payload.ts
    │   │   └── validate.ts
    │   ├── test
    │   │   ├── __snapshots__
    │   │   │   ├── address.test.ts.snap
    │   │   │   ├── apply-logic-to-fields.test.ts.snap
    │   │   │   ├── convert-field-to-answer.test.ts.snap
    │   │   │   ├── email.test.ts.snap
    │   │   │   ├── flatten-fields.test.ts.snap
    │   │   │   ├── fullname.test.ts.snap
    │   │   │   ├── html-utils.test.ts.snap
    │   │   │   ├── multiple-choice.test.ts.snap
    │   │   │   ├── number.test.ts.snap
    │   │   │   ├── phone-number.test.ts.snap
    │   │   │   ├── rating.test.ts.snap
    │   │   │   ├── short-text.test.ts.snap
    │   │   │   ├── transform-answer.test.ts.snap
    │   │   │   ├── url.test.ts.snap
    │   │   │   └── yes-no.test.ts.snap
    │   │   ├── address.test.ts
    │   │   ├── apply-logic-to-fields.test.ts
    │   │   ├── convert-field-to-answer.test.ts
    │   │   ├── date.test.ts
    │   │   ├── email.test.ts
    │   │   ├── fixtures
    │   │   │   ├── fields.json
    │   │   │   └── values.json
    │   │   ├── flatten-fields.test.ts
    │   │   ├── fullname.test.ts
    │   │   ├── html-utils.test.ts
    │   │   ├── multiple-choice.test.ts
    │   │   ├── number.test.ts
    │   │   ├── phone-number.test.ts
    │   │   ├── rating.test.ts
    │   │   ├── short-text.test.ts
    │   │   ├── transform-answer.test.ts
    │   │   ├── url.test.ts
    │   │   ├── validate-condition.test.ts
    │   │   ├── validate-payload.test.ts
    │   │   ├── validate.test.ts
    │   │   └── yes-no.test.ts
    │   ├── tsconfig.json
    │   └── tsup.config.js
    ├── embed
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── example
    │   │   └── index.html
    │   ├── global.d.ts
    │   ├── package.json
    │   ├── pnpm-lock.yaml
    │   ├── postcss.config.js
    │   ├── rollup.config.mjs
    │   ├── src
    │   │   ├── assets
    │   │   │   ├── icon-close.svg
    │   │   │   ├── icon-loading.svg
    │   │   │   └── icon-message.svg
    │   │   ├── config.ts
    │   │   ├── full-page.ts
    │   │   ├── index.ts
    │   │   ├── modal.ts
    │   │   ├── popup.ts
    │   │   ├── standard.ts
    │   │   ├── style.scss
    │   │   ├── type.ts
    │   │   └── utils
    │   │   │   ├── common.ts
    │   │   │   ├── dom.ts
    │   │   │   └── index.ts
    │   └── tsconfig.json
    ├── form-renderer
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── global-env.d.ts
    │   ├── package.json
    │   ├── postcss.config.js
    │   ├── src
    │   │   ├── Renderer.tsx
    │   │   ├── blocks
    │   │   │   ├── Address.tsx
    │   │   │   ├── Block.tsx
    │   │   │   ├── ClosedMessage.tsx
    │   │   │   ├── Country.tsx
    │   │   │   ├── Date.tsx
    │   │   │   ├── DateRange.tsx
    │   │   │   ├── Email.tsx
    │   │   │   ├── EmptyState.tsx
    │   │   │   ├── FileUpload.tsx
    │   │   │   ├── Form.tsx
    │   │   │   ├── FullName.tsx
    │   │   │   ├── InputTable.tsx
    │   │   │   ├── LegalTerms.tsx
    │   │   │   ├── LongText.tsx
    │   │   │   ├── MultipleChoice.tsx
    │   │   │   ├── Number.tsx
    │   │   │   ├── OpinionScale.tsx
    │   │   │   ├── Payment.tsx
    │   │   │   ├── PhoneNumber.tsx
    │   │   │   ├── PictureChoice.tsx
    │   │   │   ├── Rating.tsx
    │   │   │   ├── ShortText.tsx
    │   │   │   ├── Signature.tsx
    │   │   │   ├── Statement.tsx
    │   │   │   ├── SuspendedMessage.tsx
    │   │   │   ├── ThankYou.tsx
    │   │   │   ├── Website.tsx
    │   │   │   ├── Welcome.tsx
    │   │   │   ├── YesNo.tsx
    │   │   │   └── hook.ts
    │   │   ├── components
    │   │   │   ├── Button.tsx
    │   │   │   ├── ChoiceRadio.tsx
    │   │   │   ├── ChoiceRadioGroup.tsx
    │   │   │   ├── Countdown.tsx
    │   │   │   ├── CountrySelect.tsx
    │   │   │   ├── DateInput.tsx
    │   │   │   ├── DateRangeInput.tsx
    │   │   │   ├── FileUploader.tsx
    │   │   │   ├── FlagIcon.tsx
    │   │   │   ├── FormField.tsx
    │   │   │   ├── Icons
    │   │   │   │   ├── CollapseIcon.tsx
    │   │   │   │   ├── CrownIcon.tsx
    │   │   │   │   ├── EmotionIcon.tsx
    │   │   │   │   ├── LikeIcon.tsx
    │   │   │   │   ├── LinkIcon.tsx
    │   │   │   │   ├── LogoIcon.tsx
    │   │   │   │   ├── StarIcon.tsx
    │   │   │   │   ├── ThumbsUpIcon.tsx
    │   │   │   │   ├── XIcon.tsx
    │   │   │   │   └── index.ts
    │   │   │   ├── Input.tsx
    │   │   │   ├── Layout.tsx
    │   │   │   ├── Loader.tsx
    │   │   │   ├── PhoneNumberInput.tsx
    │   │   │   ├── Popup.tsx
    │   │   │   ├── Radio.tsx
    │   │   │   ├── RadioGroup.tsx
    │   │   │   ├── Rate.tsx
    │   │   │   ├── SelectHelper.tsx
    │   │   │   ├── SignaturePad.tsx
    │   │   │   ├── Slide.tsx
    │   │   │   ├── Submit.tsx
    │   │   │   ├── TableInput.tsx
    │   │   │   ├── TemporaryError.tsx
    │   │   │   ├── Textarea.tsx
    │   │   │   ├── Tooltip.tsx
    │   │   │   └── index.ts
    │   │   ├── consts
    │   │   │   ├── country.ts
    │   │   │   ├── date.ts
    │   │   │   ├── fileUpload.ts
    │   │   │   ├── index.ts
    │   │   │   ├── other.ts
    │   │   │   ├── payment.ts
    │   │   │   └── rating.tsx
    │   │   ├── i18n.ts
    │   │   ├── index.ts
    │   │   ├── locales
    │   │   │   ├── cs.ts
    │   │   │   ├── de.ts
    │   │   │   ├── en.ts
    │   │   │   ├── es.ts
    │   │   │   ├── fr.ts
    │   │   │   ├── index.ts
    │   │   │   ├── ja.ts
    │   │   │   ├── pl.ts
    │   │   │   ├── pt-br.ts
    │   │   │   ├── tr.ts
    │   │   │   ├── zh-cn.ts
    │   │   │   └── zh-tw.ts
    │   │   ├── store.ts
    │   │   ├── style.scss
    │   │   ├── theme.ts
    │   │   ├── typings.ts
    │   │   ├── utils
    │   │   │   ├── common.ts
    │   │   │   ├── form.ts
    │   │   │   ├── hook.ts
    │   │   │   ├── index.ts
    │   │   │   ├── lru.ts
    │   │   │   ├── message.ts
    │   │   │   ├── script.ts
    │   │   │   └── timeout.ts
    │   │   └── views
    │   │   │   ├── Blocks.tsx
    │   │   │   ├── Branding.tsx
    │   │   │   ├── Footer.tsx
    │   │   │   ├── Header.tsx
    │   │   │   ├── Progress.tsx
    │   │   │   └── Sidebar.tsx
    │   ├── tailwind.config.js
    │   ├── tsconfig.json
    │   └── tsup.config.js
    ├── server
    │   ├── .env.example
    │   ├── .eslintrc
    │   ├── .npmrc
    │   ├── .prettierrc
    │   ├── nest-cli.json
    │   ├── package.json
    │   ├── resources
    │   │   ├── apps.json
    │   │   └── email-templates
    │   │   │   ├── README.md
    │   │   │   ├── account_deletion_alert.html
    │   │   │   ├── account_deletion_request.html
    │   │   │   ├── email_verification_request.html
    │   │   │   ├── password_change_alert.html
    │   │   │   ├── project_deletion_alert.html
    │   │   │   ├── project_deletion_request.html
    │   │   │   ├── schedule_account_deletion_alert.html
    │   │   │   ├── submission_notification.html
    │   │   │   ├── team_deletion_alert.html
    │   │   │   ├── team_deletion_request.html
    │   │   │   └── team_invitation.html
    │   ├── src
    │   │   ├── app.module.ts
    │   │   ├── common
    │   │   │   ├── decorator
    │   │   │   │   ├── auth.decorator.ts
    │   │   │   │   ├── data-mask-options.decorator.ts
    │   │   │   │   ├── form.decorator.ts
    │   │   │   │   ├── graphql.decorator.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── permission.decorator.ts
    │   │   │   │   ├── project.decorator.ts
    │   │   │   │   ├── team.decorator.ts
    │   │   │   │   └── user.decorator.ts
    │   │   │   ├── dto
    │   │   │   │   └── index.ts
    │   │   │   ├── filter
    │   │   │   │   ├── all-exceptions.filter.ts
    │   │   │   │   └── index.ts
    │   │   │   ├── graphql
    │   │   │   │   ├── app.graphql.ts
    │   │   │   │   ├── auth.graphql.ts
    │   │   │   │   ├── endpoint.graphql.ts
    │   │   │   │   ├── form.graphql.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── integration.graphql.ts
    │   │   │   │   ├── label.graphql.ts
    │   │   │   │   ├── payment.graphql.ts
    │   │   │   │   ├── project.graphql.ts
    │   │   │   │   ├── submission.graphql.ts
    │   │   │   │   ├── team.graphql.ts
    │   │   │   │   ├── template.graphql.ts
    │   │   │   │   ├── unsplash.graphql.ts
    │   │   │   │   └── user.graphql.ts
    │   │   │   ├── guard
    │   │   │   │   ├── auth.guard.ts
    │   │   │   │   ├── browser-id.guard.ts
    │   │   │   │   ├── endpoint-anonymous-id.guard.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── permission.guard.ts
    │   │   │   │   └── role.guard.ts
    │   │   │   ├── interceptor
    │   │   │   │   ├── data-mask.interceptor.ts
    │   │   │   │   └── index.ts
    │   │   │   └── middleware
    │   │   │   │   ├── form-body.middleware.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── json-body.middleware.ts
    │   │   │   │   └── raw-body.middleware.ts
    │   │   ├── config
    │   │   │   ├── bull
    │   │   │   │   └── index.ts
    │   │   │   ├── cookie
    │   │   │   │   └── index.ts
    │   │   │   ├── graphql
    │   │   │   │   └── index.ts
    │   │   │   ├── index.ts
    │   │   │   ├── mongo
    │   │   │   │   └── index.ts
    │   │   │   ├── redis
    │   │   │   │   └── index.ts
    │   │   │   ├── smtp
    │   │   │   │   └── index.ts
    │   │   │   └── upload
    │   │   │   │   └── index.ts
    │   │   ├── controller
    │   │   │   ├── connect-stripe.controller.ts
    │   │   │   ├── dashboard.controller.ts
    │   │   │   ├── export-submissions.controller.ts
    │   │   │   ├── form.controller.ts
    │   │   │   ├── health.controller.ts
    │   │   │   ├── image.controller.ts
    │   │   │   ├── index.ts
    │   │   │   ├── payment-intent-webhook.controller.ts
    │   │   │   ├── social-login.controller.ts
    │   │   │   └── upload.controller.ts
    │   │   ├── environments
    │   │   │   └── index.ts
    │   │   ├── main.ts
    │   │   ├── model
    │   │   │   ├── app.model.ts
    │   │   │   ├── attachment.model.ts
    │   │   │   ├── form-analytic.model.ts
    │   │   │   ├── form-report.model.ts
    │   │   │   ├── form.model.ts
    │   │   │   ├── index.ts
    │   │   │   ├── integration-record.model.ts
    │   │   │   ├── integration.model.ts
    │   │   │   ├── module.ts
    │   │   │   ├── project-group.model.ts
    │   │   │   ├── project-member.model.ts
    │   │   │   ├── project.model.ts
    │   │   │   ├── submission-ip-limit.model.ts
    │   │   │   ├── submission.model.ts
    │   │   │   ├── team-activity.model.ts
    │   │   │   ├── team-invitation.model.ts
    │   │   │   ├── team-member.model.ts
    │   │   │   ├── team.model.ts
    │   │   │   ├── template.model.ts
    │   │   │   ├── user-social-account.model.ts
    │   │   │   └── user.model.ts
    │   │   ├── queue
    │   │   │   ├── base.queue.ts
    │   │   │   ├── form-report.queue.ts
    │   │   │   ├── index.ts
    │   │   │   ├── integration-email.queue.ts
    │   │   │   ├── integration-webhook.queue.ts
    │   │   │   ├── mail.queue.ts
    │   │   │   └── translate-form.queue.ts
    │   │   ├── resolver
    │   │   │   ├── app
    │   │   │   │   ├── app-detail.resolver.ts
    │   │   │   │   └── apps.resolver.ts
    │   │   │   ├── auth
    │   │   │   │   ├── login.resolver.ts
    │   │   │   │   ├── reset-password.resolver.ts
    │   │   │   │   ├── send-reset-password-email.resolver.ts
    │   │   │   │   └── sign-up.resolver.ts
    │   │   │   ├── endpoint
    │   │   │   │   ├── complete-submission.resolver.ts
    │   │   │   │   ├── form-password.resolver.ts
    │   │   │   │   └── open-form.resolver.ts
    │   │   │   ├── form
    │   │   │   │   ├── create-form-field.resolver.ts
    │   │   │   │   ├── create-form-hidden-field.resolver.ts
    │   │   │   │   ├── create-form.resolver.ts
    │   │   │   │   ├── delete-form-field.resolver.ts
    │   │   │   │   ├── delete-form-hidden-field.resolver.ts
    │   │   │   │   ├── delete-form.resolver.ts
    │   │   │   │   ├── duplicate-form.resolver.ts
    │   │   │   │   ├── form-analytic.resolver.ts
    │   │   │   │   ├── form-archive.resolver.ts
    │   │   │   │   ├── form-detail.resolver.ts
    │   │   │   │   ├── form-integrations.resolver.ts
    │   │   │   │   ├── form-report.resolver.ts
    │   │   │   │   ├── forms.resolver.ts
    │   │   │   │   ├── move-form-to-trash.resolver.ts
    │   │   │   │   ├── restore-form.resolver.ts
    │   │   │   │   ├── search-forms.resolver.ts
    │   │   │   │   ├── update-form-archive.resolver.ts
    │   │   │   │   ├── update-form-field.resolver.ts
    │   │   │   │   ├── update-form-hidden-field.resolver.ts
    │   │   │   │   ├── update-form-logics.resolver.ts
    │   │   │   │   ├── update-form-schemas.resolver.ts
    │   │   │   │   ├── update-form-theme.resolver.ts
    │   │   │   │   ├── update-form-variables.resolver.ts
    │   │   │   │   └── update-form.resolver.ts
    │   │   │   ├── index.ts
    │   │   │   ├── integration
    │   │   │   │   ├── delete-integration-settings.resolver.ts
    │   │   │   │   ├── update-integration-settings.resolver.ts
    │   │   │   │   └── update-integration-status.resolver.ts
    │   │   │   ├── payment
    │   │   │   │   ├── connect-stripe.resolver.ts
    │   │   │   │   ├── revoke-stripe-account.resolver.ts
    │   │   │   │   └── stripe-authorize-url.resolver.ts
    │   │   │   ├── project
    │   │   │   │   ├── add-project-member.resolver.ts
    │   │   │   │   ├── create-project.resolver.ts
    │   │   │   │   ├── delete-project-code.resolver.ts
    │   │   │   │   ├── delete-project-member.resolver.ts
    │   │   │   │   ├── delete-project.resolver.ts
    │   │   │   │   ├── empty-project-trash.resolver.ts
    │   │   │   │   ├── leave-project.resolver.ts
    │   │   │   │   ├── project-members.resolver.ts
    │   │   │   │   ├── projects.resolver.ts
    │   │   │   │   └── rename-project.resolver.ts
    │   │   │   ├── submission
    │   │   │   │   ├── delete-submission.resolver.ts
    │   │   │   │   ├── submission-answers.resolver.ts
    │   │   │   │   ├── submissions.resolver.ts
    │   │   │   │   └── update-submission-answer.resolver.ts
    │   │   │   ├── team
    │   │   │   │   ├── create-team.resolver.ts
    │   │   │   │   ├── dissolve-team-code.resolver.ts
    │   │   │   │   ├── dissolve-team.resolver.ts
    │   │   │   │   ├── invite-member.resolver.ts
    │   │   │   │   ├── join-team.resolver.ts
    │   │   │   │   ├── leave-team.resolver.ts
    │   │   │   │   ├── public-team-detail.resolver.ts
    │   │   │   │   ├── remove-team-member.resolver.ts
    │   │   │   │   ├── reset-team-invite-code.resolver.ts
    │   │   │   │   ├── team-members.resolver.ts
    │   │   │   │   ├── team-subscription.resolver.ts
    │   │   │   │   ├── teams.resolver.ts
    │   │   │   │   ├── transfer-team.resolver.ts
    │   │   │   │   └── update-team.resolver.ts
    │   │   │   ├── template
    │   │   │   │   ├── template-detail.resolver.ts
    │   │   │   │   ├── templates.resolver.ts
    │   │   │   │   └── use-template.resolver.ts
    │   │   │   ├── unsplash
    │   │   │   │   ├── unsplash-search.resolver.ts
    │   │   │   │   └── unsplash-track-download.resolver.ts
    │   │   │   └── user
    │   │   │   │   ├── cancel-user-deletion.resolver.ts
    │   │   │   │   ├── change-email-code.resolver.ts
    │   │   │   │   ├── email-verification-code.resolver.ts
    │   │   │   │   ├── update-email.resolver.ts
    │   │   │   │   ├── update-user-password.resolver.ts
    │   │   │   │   ├── update-user.resolver.ts
    │   │   │   │   ├── user-deletion-code.resolver.ts
    │   │   │   │   ├── user-detail.resolver.ts
    │   │   │   │   ├── verify-email.resolver.ts
    │   │   │   │   └── verify-user-deletion.resolver.ts
    │   │   ├── schedule
    │   │   │   ├── delete-form-in-trash.schedule.ts
    │   │   │   ├── delete-user-account.schedule.ts
    │   │   │   ├── index.ts
    │   │   │   └── reset-invite-code.schedule.ts
    │   │   ├── service
    │   │   │   ├── app.service.ts
    │   │   │   ├── auth.service.ts
    │   │   │   ├── endpoint.service.ts
    │   │   │   ├── export-file.service.ts
    │   │   │   ├── form-analytic.service.ts
    │   │   │   ├── form-report.service.ts
    │   │   │   ├── form.service.ts
    │   │   │   ├── index.ts
    │   │   │   ├── integration.service.ts
    │   │   │   ├── mail.service.ts
    │   │   │   ├── payment.service.ts
    │   │   │   ├── project.service.ts
    │   │   │   ├── redis.service.ts
    │   │   │   ├── schedule.service.ts
    │   │   │   ├── social-login.service.ts
    │   │   │   ├── submission-ip-limit.service.ts
    │   │   │   ├── submission.service.ts
    │   │   │   ├── team.service.ts
    │   │   │   ├── template.service.ts
    │   │   │   └── user.service.ts
    │   │   └── utils
    │   │   │   ├── anti-bot
    │   │   │       ├── akismet.ts
    │   │   │       ├── index.ts
    │   │   │       └── recaptcha.ts
    │   │   │   ├── crypto.ts
    │   │   │   ├── decorators
    │   │   │       ├── client.ts
    │   │   │       ├── device-id.ts
    │   │   │       ├── index.ts
    │   │   │       ├── ip.ts
    │   │   │       ├── lang.ts
    │   │   │       ├── raw-body.ts
    │   │   │       └── user-agent.ts
    │   │   │   ├── directives
    │   │   │       ├── index.ts
    │   │   │       └── lower.ts
    │   │   │   ├── disposable-email.ts
    │   │   │   ├── gravatar
    │   │   │       └── index.ts
    │   │   │   ├── handlebars.ts
    │   │   │   ├── index.ts
    │   │   │   ├── logger
    │   │   │       └── index.ts
    │   │   │   ├── lower-case-scalar.ts
    │   │   │   ├── map-to-object.ts
    │   │   │   ├── mongo.ts
    │   │   │   ├── promise-timeout.ts
    │   │   │   ├── random-number.ts
    │   │   │   ├── read-dir-sync.ts
    │   │   │   ├── request-parser.ts
    │   │   │   ├── smtp
    │   │   │       └── index.ts
    │   │   │   ├── social-login
    │   │   │       ├── apple.ts
    │   │   │       ├── google.ts
    │   │   │       ├── index.ts
    │   │   │       └── utils.ts
    │   │   │   ├── unsplash
    │   │   │       └── index.ts
    │   │   │   └── user-agent
    │   │   │       └── index.ts
    │   ├── tsconfig.build.json
    │   ├── tsconfig.json
    │   └── view
    │   │   ├── connect-stripe.html
    │   │   └── social-login.html
    ├── shared-types-enums
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── package.json
    │   ├── src
    │   │   ├── audience.ts
    │   │   ├── constants
    │   │   │   └── form.ts
    │   │   ├── enums
    │   │   │   ├── form.ts
    │   │   │   ├── keyboard.ts
    │   │   │   ├── social-login.ts
    │   │   │   └── submission.ts
    │   │   ├── form.ts
    │   │   ├── index.ts
    │   │   ├── submission.ts
    │   │   └── unsplash.ts
    │   ├── tsconfig.json
    │   └── tsup.config.js
    ├── utils
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── package.json
    │   ├── src
    │   │   ├── bytes.ts
    │   │   ├── clone.ts
    │   │   ├── color.ts
    │   │   ├── conv.ts
    │   │   ├── date.ts
    │   │   ├── helper.ts
    │   │   ├── index.ts
    │   │   ├── mime-db.json
    │   │   ├── mime.ts
    │   │   ├── nanoid.ts
    │   │   ├── object.ts
    │   │   ├── random.ts
    │   │   ├── second.ts
    │   │   ├── slugify.ts
    │   │   ├── type.ts
    │   │   └── uuid.ts
    │   ├── test
    │   │   ├── __snapshots__
    │   │   │   └── mime.test.ts.snap
    │   │   ├── bytes.test.ts
    │   │   ├── clone.test.ts
    │   │   ├── color.test.ts
    │   │   ├── conv.test.ts
    │   │   ├── date.test.ts
    │   │   ├── helper.test.ts
    │   │   ├── hs.test.ts
    │   │   ├── mime.test.ts
    │   │   ├── nanoid.test.ts
    │   │   ├── object.test.ts
    │   │   ├── parse.test.ts
    │   │   ├── qs.test.ts
    │   │   ├── random.test.ts
    │   │   ├── second.test.ts
    │   │   ├── slugify.test.ts
    │   │   ├── type.test.ts
    │   │   └── uuid.test.ts
    │   ├── tsconfig.json
    │   └── tsup.config.js
    └── webapp
    │   ├── .env.example
    │   ├── .eslintrc
    │   ├── .prettierrc
    │   ├── index.html
    │   ├── package.json
    │   ├── postcss.config.js
    │   ├── public
    │       └── static
    │       │   ├── apple-touch-icon.png
    │       │   ├── email.png
    │       │   ├── facebookpixel.png
    │       │   ├── favicon.ico
    │       │   ├── favicon.svg
    │       │   ├── googleanalytics.png
    │       │   ├── icon_120.png
    │       │   ├── icon_128.png
    │       │   ├── icon_180.png
    │       │   ├── icon_192.png
    │       │   ├── icon_512.png
    │       │   ├── icon_maskable_128.png
    │       │   ├── icon_maskable_192.png
    │       │   ├── icon_maskable_512.png
    │       │   ├── initial.css
    │       │   ├── manifest.webmanifest
    │       │   └── webhook.png
    │   ├── src
    │       ├── components
    │       │   ├── Async.tsx
    │       │   ├── CopyButton.tsx
    │       │   ├── DateRangePicker.tsx
    │       │   ├── DragUploader.tsx
    │       │   ├── Heading.tsx
    │       │   ├── MobilePhoneCode.tsx
    │       │   ├── Pagination.tsx
    │       │   ├── PhotoPickerField.tsx
    │       │   ├── RedirectUriLink.tsx
    │       │   ├── SubHeading.tsx
    │       │   ├── SwitchField.tsx
    │       │   ├── TagGroup.tsx
    │       │   ├── TimeInput.tsx
    │       │   ├── Uploader.tsx
    │       │   ├── icons
    │       │   │   ├── AddImageIcon.tsx
    │       │   │   ├── AppleIcon.tsx
    │       │   │   ├── BoldIcon.tsx
    │       │   │   ├── BranchIcon.tsx
    │       │   │   ├── CollapseIcon.tsx
    │       │   │   ├── ConcentricCirclesIcon.tsx
    │       │   │   ├── CrownIcon.tsx
    │       │   │   ├── DateRangeIcon.tsx
    │       │   │   ├── DateTimeIcon.tsx
    │       │   │   ├── EdgeArrow.tsx
    │       │   │   ├── EmotionIcon.tsx
    │       │   │   ├── ExpandIcon.tsx
    │       │   │   ├── FullpageIcon.tsx
    │       │   │   ├── GoogleIcon.tsx
    │       │   │   ├── ImageIcon.tsx
    │       │   │   ├── ItalicIcon.tsx
    │       │   │   ├── LayoutCoverIcon.tsx
    │       │   │   ├── LayoutFloatLeftIcon.tsx
    │       │   │   ├── LayoutFloatRightIcon.tsx
    │       │   │   ├── LayoutInlineIcon.tsx
    │       │   │   ├── LayoutSplitLeftIcon.tsx
    │       │   │   ├── LayoutSplitRightIcon.tsx
    │       │   │   ├── LikeIcon.tsx
    │       │   │   ├── LinkIcon.tsx
    │       │   │   ├── LogoIcon.tsx
    │       │   │   ├── LongTextIcon.tsx
    │       │   │   ├── ModalIcon.tsx
    │       │   │   ├── NumberVariableIcon.tsx
    │       │   │   ├── PopupIcon.tsx
    │       │   │   ├── RestoreIcon.tsx
    │       │   │   ├── RoundLogoIcon.tsx
    │       │   │   ├── ShortTextIcon.tsx
    │       │   │   ├── SignatureIcon.tsx
    │       │   │   ├── StandardIcon.tsx
    │       │   │   ├── StarIcon.tsx
    │       │   │   ├── StatementIcon.tsx
    │       │   │   ├── StringVariableIcon.tsx
    │       │   │   ├── TableIcon.tsx
    │       │   │   ├── ThankYouIcon.tsx
    │       │   │   ├── ThumbsUpIcon.tsx
    │       │   │   ├── UnderlineIcon.tsx
    │       │   │   ├── UnlinkIcon.tsx
    │       │   │   ├── WebsiteIcon.tsx
    │       │   │   ├── WelcomeIcon.tsx
    │       │   │   ├── WorkspaceIcon.tsx
    │       │   │   ├── YesOrNoIcon.tsx
    │       │   │   └── index.ts
    │       │   ├── index.ts
    │       │   ├── layouts
    │       │   │   ├── AuthGuard.tsx
    │       │   │   ├── AuthLayout.tsx
    │       │   │   ├── CommonLayout.tsx
    │       │   │   ├── FormGuardLayout.tsx
    │       │   │   ├── FormLayout.tsx
    │       │   │   ├── PublicLayout.tsx
    │       │   │   ├── WorkspaceGuard.tsx
    │       │   │   ├── WorkspaceLayout.tsx
    │       │   │   ├── index.scss
    │       │   │   └── index.ts
    │       │   ├── numberRange
    │       │   │   ├── index.tsx
    │       │   │   └── style.scss
    │       │   ├── photoPicker
    │       │   │   ├── Unsplash.tsx
    │       │   │   ├── index.tsx
    │       │   │   └── style.scss
    │       │   ├── sidebar
    │       │   │   ├── Navigation.tsx
    │       │   │   ├── UserAccount.tsx
    │       │   │   ├── WorkspaceSwitcher.tsx
    │       │   │   ├── index.scss
    │       │   │   └── index.tsx
    │       │   ├── store.ts
    │       │   └── ui
    │       │   │   ├── avatar
    │       │   │       ├── Avatar.tsx
    │       │   │       ├── Group.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── badge
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── button
    │       │   │       ├── Button.tsx
    │       │   │       ├── Group.tsx
    │       │   │       ├── Link.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── checkbox
    │       │   │       ├── Checkbox.tsx
    │       │   │       ├── Group.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── color-picker
    │       │   │       ├── AlphaInput.tsx
    │       │   │       ├── HexColorInput.tsx
    │       │   │       ├── helper.ts
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── date-picker
    │       │   │       ├── Calendar.tsx
    │       │   │       ├── MonthPicker.tsx
    │       │   │       ├── TimePicker.tsx
    │       │   │       ├── YearPicker.tsx
    │       │   │       ├── common.ts
    │       │   │       ├── index.tsx
    │       │   │       ├── store.ts
    │       │   │       ├── style.scss
    │       │   │       └── utils.ts
    │       │   │   ├── dropdown
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── empty-states
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── form
    │       │   │       ├── CustomForm.tsx
    │       │   │       ├── FormItem.tsx
    │       │   │       ├── SwitchItem.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── heading
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── hook.ts
    │       │   │   ├── icons
    │       │   │       ├── DefaultAvatarIcon.tsx
    │       │   │       ├── EyeCloseIcon.tsx
    │       │   │       └── index.ts
    │       │   │   ├── image
    │       │   │       └── index.tsx
    │       │   │   ├── index.ts
    │       │   │   ├── input
    │       │   │       ├── Input.tsx
    │       │   │       ├── Password.tsx
    │       │   │       ├── Search.tsx
    │       │   │       ├── Textarea.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── loader
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── menu
    │       │   │       ├── Divider.tsx
    │       │   │       ├── Item.tsx
    │       │   │       ├── Label.tsx
    │       │   │       ├── Menus.tsx
    │       │   │       ├── context.ts
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── modal
    │       │   │       ├── Confirm.tsx
    │       │   │       ├── Modal.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── navbar
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── notification
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── popup
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── portal
    │       │   │       └── index.tsx
    │       │   │   ├── progress
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── radio
    │       │   │       ├── Group.tsx
    │       │   │       ├── Radio.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── rate
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── select
    │       │   │       ├── Card.tsx
    │       │   │       ├── Cards.tsx
    │       │   │       ├── CardsContext.ts
    │       │   │       ├── Custom.tsx
    │       │   │       ├── Multiple.tsx
    │       │   │       ├── Native.tsx
    │       │   │       ├── index.tsx
    │       │   │       ├── style.scss
    │       │   │       └── utils.ts
    │       │   │   ├── slider
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── spin
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── stepper
    │       │   │       ├── StepperItem.tsx
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── style.scss
    │       │   │   ├── switch
    │       │   │       ├── Group.tsx
    │       │   │       ├── Switch.tsx
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── table
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── tabs
    │       │   │       ├── Pane.tsx
    │       │   │       ├── Tabs.tsx
    │       │   │       ├── context.ts
    │       │   │       ├── index.ts
    │       │   │       └── style.scss
    │       │   │   ├── tooltip
    │       │   │       ├── index.tsx
    │       │   │       └── style.scss
    │       │   │   ├── typing.ts
    │       │   │   └── utils.ts
    │       ├── consts
    │       │   ├── date.ts
    │       │   ├── environments.ts
    │       │   ├── formBuilder.ts
    │       │   ├── formSettings.ts
    │       │   ├── graphql.ts
    │       │   └── index.ts
    │       ├── locales
    │       │   ├── cs.ts
    │       │   ├── en.ts
    │       │   ├── es.ts
    │       │   ├── index.ts
    │       │   ├── pl.ts
    │       │   ├── ptBr.ts
    │       │   ├── tr.ts
    │       │   ├── zhCn.ts
    │       │   └── zhTw.ts
    │       ├── main.tsx
    │       ├── models
    │       │   ├── compose.ts
    │       │   ├── form.ts
    │       │   ├── index.ts
    │       │   ├── integration.ts
    │       │   ├── project.ts
    │       │   ├── template.ts
    │       │   ├── user.ts
    │       │   └── workspace.ts
    │       ├── pages
    │       │   ├── auth
    │       │   │   ├── ForgotPassword.tsx
    │       │   │   ├── Login.tsx
    │       │   │   ├── OauthAuthorization.tsx
    │       │   │   ├── ResetPassword.tsx
    │       │   │   ├── SignUp.tsx
    │       │   │   └── views
    │       │   │   │   └── ThirdPartyLogin.tsx
    │       │   ├── form
    │       │   │   ├── Analytics
    │       │   │   │   ├── index.tsx
    │       │   │   │   └── views
    │       │   │   │   │   ├── Report
    │       │   │   │   │       ├── AnswerList.tsx
    │       │   │   │   │       ├── FieldList.tsx
    │       │   │   │   │       ├── ReportItem.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   └── Summary.tsx
    │       │   │   ├── Create
    │       │   │   │   ├── consts
    │       │   │   │   │   ├── country.ts
    │       │   │   │   │   ├── date.ts
    │       │   │   │   │   ├── index.ts
    │       │   │   │   │   ├── layout.tsx
    │       │   │   │   │   ├── logic.ts
    │       │   │   │   │   ├── payment.ts
    │       │   │   │   │   └── rating.tsx
    │       │   │   │   ├── index.tsx
    │       │   │   │   ├── store
    │       │   │   │   │   ├── actions.ts
    │       │   │   │   │   ├── context.ts
    │       │   │   │   │   ├── hook.ts
    │       │   │   │   │   └── index.ts
    │       │   │   │   ├── style.scss
    │       │   │   │   ├── utils
    │       │   │   │   │   ├── field.ts
    │       │   │   │   │   ├── index.ts
    │       │   │   │   │   ├── logic.ts
    │       │   │   │   │   └── queue.ts
    │       │   │   │   └── views
    │       │   │   │   │   ├── Compose
    │       │   │   │   │       ├── Blocks
    │       │   │   │   │       │   ├── Address.tsx
    │       │   │   │   │       │   ├── Block.tsx
    │       │   │   │   │       │   ├── Country.tsx
    │       │   │   │   │       │   ├── Date.tsx
    │       │   │   │   │       │   ├── DateRange.tsx
    │       │   │   │   │       │   ├── Email.tsx
    │       │   │   │   │       │   ├── FileUpload.tsx
    │       │   │   │   │       │   ├── FullName.tsx
    │       │   │   │   │       │   ├── InputTable.tsx
    │       │   │   │   │       │   ├── LegalTerms.tsx
    │       │   │   │   │       │   ├── LongText.tsx
    │       │   │   │   │       │   ├── MultipleChoice.tsx
    │       │   │   │   │       │   ├── Number.tsx
    │       │   │   │   │       │   ├── OpinionScale.tsx
    │       │   │   │   │       │   ├── Payment.tsx
    │       │   │   │   │       │   ├── PhoneNumber.tsx
    │       │   │   │   │       │   ├── PictureChoice.tsx
    │       │   │   │   │       │   ├── Rating.tsx
    │       │   │   │   │       │   ├── ShortText.tsx
    │       │   │   │   │       │   ├── Signature.tsx
    │       │   │   │   │       │   ├── Statement.tsx
    │       │   │   │   │       │   ├── ThankYou.tsx
    │       │   │   │   │       │   ├── Website.tsx
    │       │   │   │   │       │   ├── Welcome.tsx
    │       │   │   │   │       │   ├── YesNo.tsx
    │       │   │   │   │       │   └── index.tsx
    │       │   │   │   │       ├── FakeRadio.tsx
    │       │   │   │   │       ├── FakeSelect.tsx
    │       │   │   │   │       ├── FakeSubmit.tsx
    │       │   │   │   │       ├── FlagIcon.tsx
    │       │   │   │   │       ├── Layout.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   ├── FieldConfig.ts
    │       │   │   │   │   ├── FieldIcon.tsx
    │       │   │   │   │   ├── LeftSidebar
    │       │   │   │   │       ├── CreateHiddenField.tsx
    │       │   │   │   │       ├── EditHiddenField.tsx
    │       │   │   │   │       ├── FieldCard.tsx
    │       │   │   │   │       ├── FieldKindIcon.tsx
    │       │   │   │   │       ├── FieldList.tsx
    │       │   │   │   │       ├── HiddenFieldCard.tsx
    │       │   │   │   │       ├── HiddenFields.tsx
    │       │   │   │   │       ├── InsertFieldDropdown.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   ├── LogicBulkEditPanel
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   ├── LogicFlow
    │       │   │   │   │       ├── ConnectionLine.tsx
    │       │   │   │   │       ├── CustomNode.tsx
    │       │   │   │   │       ├── Flow.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   ├── LogicPanel
    │       │   │   │   │       ├── Action
    │       │   │   │   │       │   ├── FieldSelect.tsx
    │       │   │   │   │       │   └── index.tsx
    │       │   │   │   │       ├── Condition
    │       │   │   │   │       │   └── index.tsx
    │       │   │   │   │       ├── PayloadForm.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   ├── RichText
    │       │   │   │   │       ├── FloatingToolbar.tsx
    │       │   │   │   │       ├── MentionMenu.tsx
    │       │   │   │   │       ├── index.tsx
    │       │   │   │   │       └── utils.ts
    │       │   │   │   │   ├── RightSidebar
    │       │   │   │   │       ├── Design
    │       │   │   │   │       │   ├── Customize
    │       │   │   │   │       │   │   ├── BackgroundBrightness.tsx
    │       │   │   │   │       │   │   ├── BackgroundImage.tsx
    │       │   │   │   │       │   │   ├── ColorPickerField.tsx
    │       │   │   │   │       │   │   ├── CustomCssHelpModal.tsx
    │       │   │   │   │       │   │   └── index.tsx
    │       │   │   │   │       │   ├── Theme
    │       │   │   │   │       │   │   └── index.tsx
    │       │   │   │   │       │   ├── index.tsx
    │       │   │   │   │       │   └── style.scss
    │       │   │   │   │       ├── Logic
    │       │   │   │   │       │   ├── KindSelect.tsx
    │       │   │   │   │       │   ├── Rules.tsx
    │       │   │   │   │       │   ├── Variables.tsx
    │       │   │   │   │       │   └── index.tsx
    │       │   │   │   │       ├── Question
    │       │   │   │   │       │   ├── CoverImage.tsx
    │       │   │   │   │       │   ├── Layout.tsx
    │       │   │   │   │       │   ├── Settings
    │       │   │   │   │       │   │   ├── Basic.tsx
    │       │   │   │   │       │   │   ├── ConnectStripeModal.tsx
    │       │   │   │   │       │   │   ├── Date.tsx
    │       │   │   │   │       │   │   ├── MultipleChoice.tsx
    │       │   │   │   │       │   │   ├── OpinionScale.tsx
    │       │   │   │   │       │   │   ├── Payment.tsx
    │       │   │   │   │       │   │   ├── PhoneNumber.tsx
    │       │   │   │   │       │   │   ├── Rating.tsx
    │       │   │   │   │       │   │   ├── Statement.tsx
    │       │   │   │   │       │   │   ├── ThankYou.tsx
    │       │   │   │   │       │   │   └── index.tsx
    │       │   │   │   │       │   ├── TypeSelect.tsx
    │       │   │   │   │       │   └── index.tsx
    │       │   │   │   │       └── index.tsx
    │       │   │   │   │   └── VariablePanel
    │       │   │   │   │       └── index.tsx
    │       │   │   ├── FormSettings
    │       │   │   │   ├── Basic.tsx
    │       │   │   │   ├── ExpirationDate.tsx
    │       │   │   │   ├── FormStatus.tsx
    │       │   │   │   ├── Protection.tsx
    │       │   │   │   ├── Sidebar.tsx
    │       │   │   │   ├── SubmissionArchive.tsx
    │       │   │   │   ├── index.tsx
    │       │   │   │   └── style.scss
    │       │   │   ├── Integration
    │       │   │   │   ├── index.tsx
    │       │   │   │   └── views
    │       │   │   │   │   ├── AppItem.tsx
    │       │   │   │   │   └── Settings
    │       │   │   │   │       ├── index.tsx
    │       │   │   │   │       └── views
    │       │   │   │   │           ├── CommonSettings.tsx
    │       │   │   │   │           ├── SettingsWrapper.tsx
    │       │   │   │   │           └── Summary.tsx
    │       │   │   ├── Render
    │       │   │   │   ├── CustomCode.tsx
    │       │   │   │   ├── PasswordCheck.tsx
    │       │   │   │   ├── index.tsx
    │       │   │   │   └── utils
    │       │   │   │   │   ├── captcha.ts
    │       │   │   │   │   ├── payment.ts
    │       │   │   │   │   └── uploader.ts
    │       │   │   ├── Submissions
    │       │   │   │   ├── SelectedPanel.tsx
    │       │   │   │   ├── index.tsx
    │       │   │   │   ├── style.scss
    │       │   │   │   └── views
    │       │   │   │   │   ├── CategorySelect.tsx
    │       │   │   │   │   ├── ExportLink.tsx
    │       │   │   │   │   └── Sheet
    │       │   │   │   │       ├── Cell.tsx
    │       │   │   │   │       ├── Columns.tsx
    │       │   │   │   │       ├── DataGrid.tsx
    │       │   │   │   │       ├── FilterRow.tsx
    │       │   │   │   │       ├── GroupCell.tsx
    │       │   │   │   │       ├── GroupRow.tsx
    │       │   │   │   │       ├── HeaderCell.tsx
    │       │   │   │   │       ├── HeaderRow.tsx
    │       │   │   │   │       ├── Row.tsx
    │       │   │   │   │       ├── SheetHeaderCell.tsx
    │       │   │   │   │       ├── SheetKindIcon.tsx
    │       │   │   │   │       ├── SheetRowModal.tsx
    │       │   │   │   │       ├── SummaryCell.tsx
    │       │   │   │   │       ├── SummaryRow.tsx
    │       │   │   │   │       ├── formatters
    │       │   │   │   │           ├── SelectCellFormatter.tsx
    │       │   │   │   │           ├── ToggleGroupFormatter.tsx
    │       │   │   │   │           ├── ValueFormatter.tsx
    │       │   │   │   │           └── index.ts
    │       │   │   │   │       ├── headerCells
    │       │   │   │   │           └── SortableHeaderCell.tsx
    │       │   │   │   │       ├── hooks
    │       │   │   │   │           ├── index.ts
    │       │   │   │   │           ├── useFocusRef.ts
    │       │   │   │   │           ├── useGridDimensions.ts
    │       │   │   │   │           ├── useLatestFunc.ts
    │       │   │   │   │           ├── useViewportColumns.ts
    │       │   │   │   │           └── useViewportRows.ts
    │       │   │   │   │       ├── index.tsx
    │       │   │   │   │       ├── sheetCells
    │       │   │   │   │           ├── AddressCell.tsx
    │       │   │   │   │           ├── DateRangeCell.tsx
    │       │   │   │   │           ├── DropPickerCell.tsx
    │       │   │   │   │           ├── FileUploadCell.tsx
    │       │   │   │   │           ├── FullNameCell.tsx
    │       │   │   │   │           ├── HiddenFieldCell.tsx
    │       │   │   │   │           ├── InputTableCell.tsx
    │       │   │   │   │           ├── MultipleChoiceCell.tsx
    │       │   │   │   │           ├── OpinionScaleCell.tsx
    │       │   │   │   │           ├── PaymentCell.tsx
    │       │   │   │   │           ├── PictureChoiceCell.tsx
    │       │   │   │   │           ├── SignatureCell.tsx
    │       │   │   │   │           ├── SubmitDateCell.tsx
    │       │   │   │   │           ├── TextCell.tsx
    │       │   │   │   │           ├── UrlCell.tsx
    │       │   │   │   │           └── index.tsx
    │       │   │   │   │       ├── types.ts
    │       │   │   │   │       └── utils
    │       │   │   │   │           ├── index.ts
    │       │   │   │   │           ├── keyboardUtils.ts
    │       │   │   │   │           └── selectedCellUtils.ts
    │       │   │   └── views
    │       │   │   │   ├── CreateWorkspaceModal.tsx
    │       │   │   │   ├── FormComponents
    │       │   │   │       ├── Renderer.tsx
    │       │   │   │       ├── blocks
    │       │   │   │       │   ├── Address.tsx
    │       │   │   │       │   ├── Block.tsx
    │       │   │   │       │   ├── ClosedMessage.tsx
    │       │   │   │       │   ├── Country.tsx
    │       │   │   │       │   ├── Date.tsx
    │       │   │   │       │   ├── DateRange.tsx
    │       │   │   │       │   ├── Email.tsx
    │       │   │   │       │   ├── EmptyState.tsx
    │       │   │   │       │   ├── FileUpload.tsx
    │       │   │   │       │   ├── Form.tsx
    │       │   │   │       │   ├── FullName.tsx
    │       │   │   │       │   ├── InputTable.tsx
    │       │   │   │       │   ├── LegalTerms.tsx
    │       │   │   │       │   ├── LongText.tsx
    │       │   │   │       │   ├── MultipleChoice.tsx
    │       │   │   │       │   ├── Number.tsx
    │       │   │   │       │   ├── OpinionScale.tsx
    │       │   │   │       │   ├── Payment.tsx
    │       │   │   │       │   ├── PhoneNumber.tsx
    │       │   │   │       │   ├── PictureChoice.tsx
    │       │   │   │       │   ├── Rating.tsx
    │       │   │   │       │   ├── ShortText.tsx
    │       │   │   │       │   ├── Signature.tsx
    │       │   │   │       │   ├── Statement.tsx
    │       │   │   │       │   ├── ThankYou.tsx
    │       │   │   │       │   ├── Website.tsx
    │       │   │   │       │   ├── Welcome.tsx
    │       │   │   │       │   ├── YesNo.tsx
    │       │   │   │       │   └── hook.ts
    │       │   │   │       ├── components
    │       │   │   │       │   ├── ChoiceRadio.tsx
    │       │   │   │       │   ├── ChoiceRadioGroup.tsx
    │       │   │   │       │   ├── Countdown.tsx
    │       │   │   │       │   ├── CountrySelect.tsx
    │       │   │   │       │   ├── DateInput.tsx
    │       │   │   │       │   ├── DateRangeInput.tsx
    │       │   │   │       │   ├── FileUploader.tsx
    │       │   │   │       │   ├── FlagIcon.tsx
    │       │   │   │       │   ├── FormField.tsx
    │       │   │   │       │   ├── Icons
    │       │   │   │       │   │   ├── CollapseIcon.tsx
    │       │   │   │       │   │   ├── CrownIcon.tsx
    │       │   │   │       │   │   ├── EmotionIcon.tsx
    │       │   │   │       │   │   ├── LikeIcon.tsx
    │       │   │   │       │   │   ├── LinkIcon.tsx
    │       │   │   │       │   │   ├── LogoIcon.tsx
    │       │   │   │       │   │   ├── StarIcon.tsx
    │       │   │   │       │   │   ├── ThumbsUpIcon.tsx
    │       │   │   │       │   │   └── index.ts
    │       │   │   │       │   ├── Input.tsx
    │       │   │   │       │   ├── Layout.tsx
    │       │   │   │       │   ├── PhoneNumberInput.tsx
    │       │   │   │       │   ├── Radio.tsx
    │       │   │   │       │   ├── RadioGroup.tsx
    │       │   │   │       │   ├── SelectHelper.tsx
    │       │   │   │       │   ├── SignaturePad.tsx
    │       │   │   │       │   ├── Slide.tsx
    │       │   │   │       │   ├── Submit.tsx
    │       │   │   │       │   ├── TableInput.tsx
    │       │   │   │       │   ├── TemporaryError.tsx
    │       │   │   │       │   ├── Textarea.tsx
    │       │   │   │       │   └── index.ts
    │       │   │   │       ├── consts
    │       │   │   │       │   ├── country.ts
    │       │   │   │       │   ├── date.ts
    │       │   │   │       │   ├── fileUpload.ts
    │       │   │   │       │   ├── index.ts
    │       │   │   │       │   ├── motion.ts
    │       │   │   │       │   ├── payment.ts
    │       │   │   │       │   └── rating.tsx
    │       │   │   │       ├── index.ts
    │       │   │   │       ├── locales
    │       │   │   │       │   ├── cs.ts
    │       │   │   │       │   ├── de.ts
    │       │   │   │       │   ├── en.ts
    │       │   │   │       │   ├── es.ts
    │       │   │   │       │   ├── fr.ts
    │       │   │   │       │   ├── index.ts
    │       │   │   │       │   ├── pl.ts
    │       │   │   │       │   ├── pt-br.ts
    │       │   │   │       │   ├── tr.ts
    │       │   │   │       │   ├── zh-cn.ts
    │       │   │   │       │   └── zh-tw.ts
    │       │   │   │       ├── store.ts
    │       │   │   │       ├── theme.ts
    │       │   │   │       ├── typings.ts
    │       │   │   │       ├── utils
    │       │   │   │       │   ├── browser-language.ts
    │       │   │   │       │   ├── form.ts
    │       │   │   │       │   ├── hook.ts
    │       │   │   │       │   ├── index.ts
    │       │   │   │       │   ├── lru.ts
    │       │   │   │       │   ├── message.ts
    │       │   │   │       │   ├── script.ts
    │       │   │   │       │   └── timeout.ts
    │       │   │   │       └── views
    │       │   │   │       │   ├── Blocks.tsx
    │       │   │   │       │   ├── Branding.tsx
    │       │   │   │       │   ├── Footer.tsx
    │       │   │   │       │   ├── Header.tsx
    │       │   │   │       │   ├── Progress.tsx
    │       │   │   │       │   └── Sidebar.tsx
    │       │   │   │   ├── FormEmbedModal
    │       │   │   │       ├── EmbedPreview.tsx
    │       │   │   │       ├── FullpageSettings.tsx
    │       │   │   │       ├── ModalSettings.tsx
    │       │   │   │       ├── PopupSettings.tsx
    │       │   │   │       ├── StandardSettings.tsx
    │       │   │   │       ├── WidthInput.tsx
    │       │   │   │       ├── index.scss
    │       │   │   │       └── index.tsx
    │       │   │   │   ├── FormNavbar
    │       │   │   │       ├── FormActions.tsx
    │       │   │   │       ├── Navigation.tsx
    │       │   │   │       ├── UserAccount.tsx
    │       │   │   │       └── index.tsx
    │       │   │   │   ├── FormPreviewModal
    │       │   │   │       ├── index.scss
    │       │   │   │       └── index.tsx
    │       │   │   │   └── FormShareModal.tsx
    │       │   ├── home
    │       │   │   └── Home.tsx
    │       │   ├── project
    │       │   │   ├── Project
    │       │   │   │   ├── index.scss
    │       │   │   │   ├── index.tsx
    │       │   │   │   └── views
    │       │   │   │   │   ├── CreateForm.tsx
    │       │   │   │   │   └── RenameForm.tsx
    │       │   │   ├── Trash.tsx
    │       │   │   └── views
    │       │   │   │   ├── DeleteProject.tsx
    │       │   │   │   ├── ProjectLayout.tsx
    │       │   │   │   ├── ProjectMembers.tsx
    │       │   │   │   ├── RenameProject.tsx
    │       │   │   │   ├── Skeleton.tsx
    │       │   │   │   └── index.scss
    │       │   ├── user
    │       │   │   ├── UserSettings
    │       │   │   │   ├── Avatar.tsx
    │       │   │   │   ├── DeleteAccount.tsx
    │       │   │   │   ├── EmailAddress.tsx
    │       │   │   │   ├── Password.tsx
    │       │   │   │   ├── UserName.tsx
    │       │   │   │   └── index.tsx
    │       │   │   └── VerifyEmail.tsx
    │       │   └── workspace
    │       │   │   ├── CreateWorkspace.tsx
    │       │   │   ├── JoinWorkspace.tsx
    │       │   │   ├── Members
    │       │   │       ├── DeleteMember.tsx
    │       │   │       ├── InviteMember.tsx
    │       │   │       ├── LeaveWorkspace.tsx
    │       │   │       ├── TransferWorkspace.tsx
    │       │   │       └── index.tsx
    │       │   │   ├── Workspace
    │       │   │       ├── AssignMember.tsx
    │       │   │       ├── CreateProject.tsx
    │       │   │       └── index.tsx
    │       │   │   └── WorkspaceSettings
    │       │   │       ├── Branding.tsx
    │       │   │       ├── DeleteWorkspace.tsx
    │       │   │       └── index.tsx
    │       ├── router
    │       │   ├── config.ts
    │       │   └── index.tsx
    │       ├── service
    │       │   ├── app.service.ts
    │       │   ├── auth.service.ts
    │       │   ├── form.service.ts
    │       │   ├── index.ts
    │       │   ├── integration.service.ts
    │       │   ├── payment.service.ts
    │       │   ├── project.service.ts
    │       │   ├── submission.service.ts
    │       │   ├── template.service.ts
    │       │   ├── unsplash.service.ts
    │       │   ├── user.service.ts
    │       │   └── workspace.service.ts
    │       ├── store
    │       │   ├── app.store.ts
    │       │   ├── form.store.ts
    │       │   ├── index.ts
    │       │   ├── integration.store.ts
    │       │   ├── mobxStorage.ts
    │       │   ├── user.store.ts
    │       │   └── workspace.store.ts
    │       ├── styles
    │       │   ├── base.scss
    │       │   ├── components.scss
    │       │   └── index.scss
    │       ├── typings.d.ts
    │       └── utils
    │       │   ├── auth.ts
    │       │   ├── helper.ts
    │       │   ├── hook.ts
    │       │   ├── index.ts
    │       │   └── request.ts
    │   ├── tailwind.config.js
    │   ├── tsconfig.json
    │   └── vite.config.ts
├── pnpm-lock.yaml
└── pnpm-workspace.yaml


/.dockerignore:
--------------------------------------------------------------------------------
 1 | # IDE
 2 | /.idea
 3 | /.awcache
 4 | /.cache
 5 | /.vscode
 6 | 
 7 | # tests
 8 | **/coverage
 9 | **/coverage-e2e
10 | **/logs
11 | .nyc_output
12 | 
13 | **/node_modules
14 | .git
15 | .github
16 | .DS_Store
17 | 
18 | # production
19 | docker.env
20 | **/.env.local
21 | **/.env.*.local
22 | **/.env.example
23 | docker-compose.yml
24 | 


--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
 1 | root = true
 2 | 
 3 | [*]
 4 | charset = utf-8
 5 | indent_style = tab
 6 | indent_size = 2
 7 | end_of_line = lf
 8 | insert_final_newline = true
 9 | trim_trailing_whitespace = true
10 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | 
3 | github: heyform
4 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 |   - name: Feature request
4 |     url: https://github.com/orgs/heyform/discussions/new?category=ideas
5 |     about: Request a feature to be added to the project
6 |   - name: Self hosting questions
7 |     url: https://github.com/orgs/heyform/discussions/new?category=self-hosting
8 |     about: Ask questions and discuss running HeyForm with community members
9 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # dependencies
 2 | node_modules
 3 | package-lock.json
 4 | 
 5 | # IDE
 6 | /.idea
 7 | /.awcache
 8 | /.cache
 9 | /.vscode
10 | 
11 | # misc
12 | npm-debug.log
13 | yarn-error.log
14 | .DS_Store
15 | *.swp
16 | *.tar
17 | *.tar.gz
18 | *.zip
19 | *.p8
20 | 
21 | # tests
22 | coverage
23 | coverage-e2e
24 | logs
25 | .nyc_output
26 | 
27 | # environment
28 | .env
29 | .env.*
30 | docker.env
31 | !.env.example
32 | 
33 | # production
34 | dist
35 | lib
36 | packages/server/static
37 | *.tsbuildinfo
38 | 
39 | 


--------------------------------------------------------------------------------
/assets/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/assets/images/screenshot.png


--------------------------------------------------------------------------------
/packages/answer-utils/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 100,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid"
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/answer-to-api-object.ts:
--------------------------------------------------------------------------------
 1 | import { parsePlainAnswer } from './answer-to-plain'
 2 | import { Answer } from '@heyform-inc/shared-types-enums'
 3 | 
 4 | export function answersToApiObject(answers: Answer[]): Record<string, any> {
 5 |   const result: Record<string, any> = {}
 6 | 
 7 |   answers.forEach(answer => {
 8 |     const key = `(ID: ${answer.id}) ${answer.title}`
 9 |     result[key] = parsePlainAnswer(answer)
10 |   })
11 | 
12 |   return result
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/consts.ts:
--------------------------------------------------------------------------------
 1 | export const CURRENCY_SYMBOLS: Record<string, string> = {
 2 |   EUR: '\u20ac',
 3 |   GBP: '\xa3',
 4 |   USD: '
#39;,
 5 |   AUD: 'A
#39;,
 6 |   CAD: 'CA
#39;,
 7 |   CHF: 'CHF ',
 8 |   NOK: 'NOK ',
 9 |   SEK: 'SEK ',
10 |   DKK: 'DKK ',
11 |   MXN: 'MX
#39;,
12 |   NZD: 'NZ
#39;,
13 |   BRL: 'R
#39;
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/flatten-fields.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum, FormField } from '@heyform-inc/shared-types-enums'
 2 | import { helper } from '@heyform-inc/utils'
 3 | 
 4 | export function flattenFields(fields?: FormField[], withGroup = false): FormField[] {
 5 |   if (helper.isEmpty(fields)) {
 6 |     return []
 7 |   }
 8 | 
 9 |   return fields!.reduce((prev: FormField[], curr) => {
10 |     if (curr.kind === FieldKindEnum.GROUP) {
11 |       if (withGroup) {
12 |         const group = {
13 |           ...{},
14 |           ...curr,
15 |           properties: {
16 |             ...curr.properties,
17 |             fields: []
18 |           }
19 |         }
20 | 
21 |         return [...prev, group, ...(curr.properties?.fields || [])]
22 |       }
23 | 
24 |       return [...prev, ...(curr.properties?.fields || [])]
25 |     }
26 |     return [...prev, curr]
27 |   }, [])
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/hidden-fields-to-html.ts:
--------------------------------------------------------------------------------
 1 | import { HiddenFieldAnswer } from '@heyform-inc/shared-types-enums'
 2 | 
 3 | export function hiddenFieldsToHtml(hiddenFields: HiddenFieldAnswer[]): string {
 4 |   if (!hiddenFields.length) return ''
 5 | 
 6 |   const html = hiddenFields
 7 |     .map(hiddenField => {
 8 |       return `
 9 | <li>
10 |   <h3>${hiddenField.name}</h3>
11 |   <p>${hiddenField.value}</p>
12 | </li>
13 | `
14 |     })
15 |     .join('')
16 | 
17 |   return `<ol>${html}</ol>`
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './answer-to-html'
 2 | export * from './answer-to-api-object'
 3 | export * from './answer-to-plain'
 4 | export * from './answer-to-json'
 5 | export * from './hidden-fields-to-html'
 6 | export * from './field-values-to-answer'
 7 | export * from './fields-to-validate-rules'
 8 | export * from './validate'
 9 | export * from './html-utils'
10 | export * from './flatten-fields'
11 | export * from './validate-condition'
12 | export * from './validate-payload'
13 | export * from './apply-logic-to-fields'
14 | export * from './consts'
15 | export * from './helper'
16 | 


--------------------------------------------------------------------------------
/packages/answer-utils/src/validate-payload.ts:
--------------------------------------------------------------------------------
 1 | import { ActionEnum, ComparisonEnum, LogicPayload } from '@heyform-inc/shared-types-enums'
 2 | import { helper } from '@heyform-inc/utils'
 3 | 
 4 | const OTHER_COMPARISONS = [ComparisonEnum.IS_EMPTY, ComparisonEnum.IS_NOT_EMPTY]
 5 | 
 6 | export function validatePayload(payload: LogicPayload): boolean {
 7 |   if (
 8 |     !payload.action.kind ||
 9 |     (!OTHER_COMPARISONS.includes(payload.condition.comparison) &&
10 |       helper.isEmpty((payload.condition as any).expected))
11 |   ) {
12 |     return false
13 |   }
14 | 
15 |   if (payload.action.kind === ActionEnum.NAVIGATE) {
16 |     return helper.isValid(payload.action.fieldId)
17 |   }
18 | 
19 |   return (
20 |     helper.isValid(payload.action.variable) &&
21 |     helper.isValid(payload.action.operator) &&
22 |     helper.isValid(payload.action.value)
23 |   )
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/answer-utils/test/__snapshots__/address.test.ts.snap:
--------------------------------------------------------------------------------
 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 2 | 
 3 | exports[`correct value should be verified 1`] = `
 4 | [
 5 |   {
 6 |     "id": "ADDRESS",
 7 |     "kind": "address",
 8 |     "properties": {},
 9 |     "title": "address.test",
10 |     "value": {
11 |       "address1": "address1",
12 |       "city": "city",
13 |       "country": "country",
14 |       "state": "state",
15 |       "zip": "zip",
16 |     },
17 |   },
18 | ]
19 | `;
20 | 
21 | exports[`undefined value should be verified if not required 1`] = `
22 | [
23 |   {
24 |     "id": "ADDRESS",
25 |     "kind": "address",
26 |     "properties": {},
27 |     "title": "address.test",
28 |     "value": "",
29 |   },
30 | ]
31 | `;
32 | 


--------------------------------------------------------------------------------
/packages/answer-utils/test/__snapshots__/email.test.ts.snap:
--------------------------------------------------------------------------------
 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 2 | 
 3 | exports[`correct value should be verified 1`] = `
 4 | [
 5 |   {
 6 |     "id": "EMAIL",
 7 |     "kind": "email",
 8 |     "properties": {},
 9 |     "title": "number.test",
10 |     "value": "email@example.com",
11 |   },
12 | ]
13 | `;
14 | 
15 | exports[`undefined value should be verified if not required 1`] = `
16 | [
17 |   {
18 |     "id": "EMAIL",
19 |     "kind": "email",
20 |     "properties": {},
21 |     "title": "number.test",
22 |     "value": "",
23 |   },
24 | ]
25 | `;
26 | 


--------------------------------------------------------------------------------
/packages/answer-utils/test/__snapshots__/number.test.ts.snap:
--------------------------------------------------------------------------------
 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 2 | 
 3 | exports[`correct value should be verified 1`] = `
 4 | [
 5 |   {
 6 |     "id": "NUMBER",
 7 |     "kind": "number",
 8 |     "properties": {},
 9 |     "title": "number.test",
10 |     "value": 5,
11 |   },
12 | ]
13 | `;
14 | 
15 | exports[`should be verified if value greater then 5 1`] = `
16 | [
17 |   {
18 |     "id": "NUMBER",
19 |     "kind": "number",
20 |     "properties": {},
21 |     "title": "number.test",
22 |     "value": 7,
23 |   },
24 | ]
25 | `;
26 | 
27 | exports[`undefined value should be verified if not required 1`] = `
28 | [
29 |   {
30 |     "id": "NUMBER",
31 |     "kind": "number",
32 |     "properties": {},
33 |     "title": "number.test",
34 |     "value": "",
35 |   },
36 | ]
37 | `;
38 | 


--------------------------------------------------------------------------------
/packages/answer-utils/test/__snapshots__/phone-number.test.ts.snap:
--------------------------------------------------------------------------------
 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 2 | 
 3 | exports[`correct value should be verified 1`] = `
 4 | [
 5 |   {
 6 |     "id": "PHONE_NUMBER",
 7 |     "kind": "phone_number",
 8 |     "properties": {},
 9 |     "title": "phone-number.test",
10 |     "value": "+12015550123",
11 |   },
12 | ]
13 | `;
14 | 
15 | exports[`undefined value should be verified if not required 1`] = `
16 | [
17 |   {
18 |     "id": "PHONE_NUMBER",
19 |     "kind": "phone_number",
20 |     "properties": {},
21 |     "title": "phone-number.test",
22 |     "value": "",
23 |   },
24 | ]
25 | `;
26 | 


--------------------------------------------------------------------------------
/packages/answer-utils/test/__snapshots__/url.test.ts.snap:
--------------------------------------------------------------------------------
 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 2 | 
 3 | exports[`correct value should be verified 1`] = `
 4 | [
 5 |   {
 6 |     "id": "URL",
 7 |     "kind": "url",
 8 |     "properties": {},
 9 |     "title": "url.test",
10 |     "value": "https://www.google.com",
11 |   },
12 | ]
13 | `;
14 | 
15 | exports[`undefined value should be verified if not required 1`] = `
16 | [
17 |   {
18 |     "id": "URL",
19 |     "kind": "url",
20 |     "properties": {},
21 |     "title": "url.test",
22 |     "value": "",
23 |   },
24 | ]
25 | `;
26 | 


--------------------------------------------------------------------------------
/packages/answer-utils/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2017",
 4 |     "module": "commonjs",
 5 |     "forceConsistentCasingInFileNames": true,
 6 |     "esModuleInterop": true,
 7 |     "noUnusedLocals": true,
 8 |     "noUnusedParameters": true,
 9 |     "strict": true,
10 |     "removeComments": true,
11 |     "allowSyntheticDefaultImports": true,
12 |     "listEmittedFiles": true,
13 |     "listFiles": true,
14 |     "allowJs": false,
15 |     "declaration": true,
16 |     "sourceMap": true,
17 |     "importHelpers": true,
18 |     "resolveJsonModule": true,
19 |     "skipLibCheck": true
20 |   },
21 |   "exclude": [
22 |     "coverage",
23 |     "lib",
24 |     "node_modules"
25 |   ]
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/answer-utils/tsup.config.js:
--------------------------------------------------------------------------------
 1 | /** @type {import('tsup').Options} */
 2 | module.exports = {
 3 | 	target: 'esnext',
 4 | 	dts: true,
 5 | 	sourcemap: true,
 6 | 	entry: ['src/index.ts'],
 7 | 	format: ['cjs', 'esm'],
 8 | 	splitting: false,
 9 | 	treeshake: true,
10 | 	clean: true
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/embed/.eslintrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "parser": "@typescript-eslint/parser",
 3 |   "parserOptions": {
 4 |     "ecmaVersion": 2020,
 5 |     "sourceType": "module",
 6 |     "project": "./tsconfig.json"
 7 |   },
 8 |   "extends": [
 9 |     "plugin:@typescript-eslint/eslint-recommended",
10 |     "plugin:@typescript-eslint/recommended",
11 |     "plugin:prettier/recommended",
12 |     "prettier"
13 |   ],
14 |   "plugins": [
15 |     "@typescript-eslint"
16 |   ],
17 |   "env": {
18 |     "browser": true,
19 |     "es2021": true
20 |   },
21 |   "rules": {
22 |     "@typescript-eslint/no-unused-expressions": "off",
23 |     "@typescript-eslint/no-explicit-any": "off",
24 |     "prettier/prettier": "error"
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/embed/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 100,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid",
11 |   "plugins": ["@trivago/prettier-plugin-sort-imports"],
12 |   "importOrder": [
13 |     "^@/",
14 |     "^[../]",
15 |     "^[./]"
16 |   ],
17 |   "importOrderSeparation": true,
18 |   "importOrderSortSpecifiers": true,
19 |   "importOrderGroupNamespaceSpecifiers": false
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/embed/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 |   const content: string
3 |   export default content
4 | }
5 | 


--------------------------------------------------------------------------------
/packages/embed/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   plugins: [
3 |     require('autoprefixer')
4 |   ]
5 | }
6 | 


--------------------------------------------------------------------------------
/packages/embed/src/assets/icon-close.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
2 |      stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3 |     <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
4 |     <path d="M18 6l-12 12"/>
5 |     <path d="M6 6l12 12"/>
6 | </svg>


--------------------------------------------------------------------------------
/packages/embed/src/assets/icon-loading.svg:
--------------------------------------------------------------------------------
1 | <svg width="22" height="5" viewBox="0 0 21 5" fill="none" xmlns="http://www.w3.org/2000/svg" class="heyform__loading">
2 |     <rect class="heyform__loading-dot" width="5" height="5" rx="2.5" fill="currentColor"/>
3 |     <rect class="heyform__loading-dot" x="8" width="5" height="5" rx="2.5" fill="currentColor"/>
4 |     <rect class="heyform__loading-dot" x="16" width="5" height="5" rx="2.5" fill="currentColor"/>
5 | </svg>


--------------------------------------------------------------------------------
/packages/embed/src/assets/icon-message.svg:
--------------------------------------------------------------------------------
1 | <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
2 |      stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3 |     <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
4 |     <path d="M12 11v.01"/>
5 |     <path d="M8 11v.01"/>
6 |     <path d="M16 11v.01"/>
7 |     <path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3z"/>
8 | </svg>


--------------------------------------------------------------------------------
/packages/embed/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common'
2 | export * from './dom'
3 | 


--------------------------------------------------------------------------------
/packages/embed/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "baseUrl": ".",
 4 |     "paths": {
 5 |       "@/*": ["src/*"]
 6 |     },
 7 |     "module": "esnext",
 8 |     "moduleResolution": "node",
 9 |     "target": "es5",
10 |     "lib": ["es6", "dom"],
11 |     "sourceMap": true,
12 |     "allowJs": false,
13 |     "skipLibCheck": true,
14 |     "esModuleInterop": true,
15 |     "allowSyntheticDefaultImports": true,
16 |     "strict": true,
17 |     "forceConsistentCasingInFileNames": true,
18 |     "noFallthroughCasesInSwitch": true,
19 |     "resolveJsonModule": true,
20 |     "isolatedModules": true,
21 |     "noEmit": true
22 |   },
23 |   "include": ["src", "global.d.ts"]
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/form-renderer/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 100,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "bracketSameLine": false,
10 |   "arrowParens": "avoid",
11 |   "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
12 |   "importOrder": ["^@/", "^[../]", "^[./]"],
13 |   "importOrderSeparation": true,
14 |   "importOrderSortSpecifiers": true,
15 |   "importOrderGroupNamespaceSpecifiers": false
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/form-renderer/global-env.d.ts:
--------------------------------------------------------------------------------
 1 | declare global {
 2 |   interface Window {
 3 |     heyform: {
 4 |       device: {
 5 |         ios: boolean
 6 |         android: boolean
 7 |         mobile: boolean
 8 |         windowHeight: number
 9 |         screenHeight: number
10 |       }
11 |     }
12 |   }
13 | }
14 | 
15 | export {}
16 | 


--------------------------------------------------------------------------------
/packages/form-renderer/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   plugins: {
3 | 		tailwindcss: require("./tailwind.config.js"),
4 |     autoprefixer: {},
5 |   },
6 | }
7 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/blocks/Statement.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { BlockProps } from './Block'
 4 | import { Block } from './Block'
 5 | import { Form } from './Form'
 6 | 
 7 | export const Statement: FC<BlockProps> = ({ field, ...restProps }) => {
 8 |   return (
 9 |     <Block className="heyform-statement heyform-empty-state" field={field} {...restProps}>
10 |       <Form field={field} />
11 |     </Block>
12 |   )
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/blocks/SuspendedMessage.tsx:
--------------------------------------------------------------------------------
 1 | import { useTranslation } from '../utils'
 2 | import { ThankYou } from './ThankYou'
 3 | 
 4 | export const SuspendedMessage = () => {
 5 |   const { t } = useTranslation()
 6 | 
 7 |   const field: any = {
 8 |     title: t("This page doesn't exist"),
 9 |     description: t('If you have any questions, please contact us.')
10 |   }
11 | 
12 |   return <ThankYou field={field} />
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/FlagIcon.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import { IComponentProps } from '../typings'
 5 | 
 6 | export interface FlagIconProps extends IComponentProps {
 7 |   countryCode?: string
 8 | }
 9 | 
10 | export const FlagIcon: FC<FlagIconProps> = ({ className, countryCode = 'US' }) => {
11 |   return <span className={clsx(`fi bg-black/10 fi-${countryCode?.toLowerCase()}`, className)} />
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/Icons/CollapseIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../../typings'
 4 | 
 5 | export const CollapseIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 6 |   <svg width="8" height="6" viewBox="0 0 8 6" xmlns="http://www.w3.org/2000/svg" {...props}>
 7 |     <path d="M4 6l4-6H0z" fillRule="evenodd" fill="currentColor"></path>
 8 |   </svg>
 9 | )
10 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/Icons/LikeIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../../typings'
 4 | 
 5 | export const LikeIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 6 |   return (
 7 |     <svg
 8 |       width="24"
 9 |       height="24"
10 |       viewBox="0 0 24 24"
11 |       fill="none"
12 |       xmlns="http://www.w3.org/2000/svg"
13 |       className="heyform-icon"
14 |       {...props}
15 |     >
16 |       <path
17 |         d="M7.5 4C4.46244 4 2 6.46245 2 9.5C2 15 8.5 20 12 21.1631C15.5 20 22 15 22 9.5C22 6.46245 19.5375 4 16.5 4C14.6399 4 12.9954 4.92345 12 6.3369C11.0046 4.92345 9.36015 4 7.5 4Z"
18 |         className="heyform-icon-fill heyform-icon-stroke"
19 |         strokeWidth="1"
20 |         strokeLinecap="round"
21 |         strokeLinejoin="round"
22 |       />
23 |     </svg>
24 |   )
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/Icons/StarIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../../typings'
 4 | 
 5 | export const StarIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 6 |   return (
 7 |     <svg
 8 |       width="24"
 9 |       height="24"
10 |       viewBox="0 0 24 24"
11 |       fill="none"
12 |       xmlns="http://www.w3.org/2000/svg"
13 |       className="heyform-icon"
14 |       {...props}
15 |     >
16 |       <path
17 |         d="M11.9993 2.5L8.9428 8.7388L2 9.74555L7.02945 14.6625L5.8272 21.5L11.9993 18.2096L18.1727 21.5L16.9793 14.6625L22 9.74555L15.0956 8.7388L11.9993 2.5Z"
18 |         className="heyform-icon-fill heyform-icon-stroke"
19 |         strokeWidth="1"
20 |         strokeLinejoin="round"
21 |       />
22 |     </svg>
23 |   )
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/Icons/XIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../../typings'
 4 | 
 5 | export const XIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 6 |   return (
 7 |     <svg
 8 |       xmlns="http://www.w3.org/2000/svg"
 9 |       width="24"
10 |       height="24"
11 |       viewBox="0 0 24 24"
12 |       fill="none"
13 |       stroke="currentColor"
14 |       strokeWidth="2"
15 |       strokeLinecap="round"
16 |       strokeLinejoin="round"
17 |       {...props}
18 |     >
19 |       <path stroke="none" d="M0 0h24v24H0z" fill="none" />
20 |       <path d="M18 6l-12 12" />
21 |       <path d="M6 6l12 12" />
22 |     </svg>
23 |   )
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/Icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CollapseIcon'
2 | export * from './CrownIcon'
3 | export * from './EmotionIcon'
4 | export * from './LikeIcon'
5 | export * from './LogoIcon'
6 | export * from './StarIcon'
7 | export * from './ThumbsUpIcon'
8 | export * from './XIcon'
9 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/components/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './Button'
 2 | export * from './ChoiceRadioGroup'
 3 | export * from './CountrySelect'
 4 | export * from './DateInput'
 5 | export * from './DateRangeInput'
 6 | export * from './FileUploader'
 7 | export * from './FlagIcon'
 8 | export * from './FormField'
 9 | export * from './Icons'
10 | export * from './Input'
11 | export * from './Layout'
12 | export * from './Loader'
13 | export * from './PhoneNumberInput'
14 | export * from './Popup'
15 | export * from './Radio'
16 | export * from './RadioGroup'
17 | export * from './Rate'
18 | export * from './SelectHelper'
19 | export * from './SignaturePad'
20 | export * from './Slide'
21 | export * from './Submit'
22 | export * from './TableInput'
23 | export * from './TemporaryError'
24 | export * from './Textarea'
25 | export * from './Tooltip'
26 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/consts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './country'
2 | export * from './date'
3 | export * from './fileUpload'
4 | export * from './other'
5 | export * from './payment'
6 | export * from './rating'
7 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/consts/other.ts:
--------------------------------------------------------------------------------
1 | export const TRANSITION_UNMOUNTED_STATES = ['exited', 'unmounted']
2 | export const CHAR_A_KEY_CODE = 65
3 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/consts/payment.ts:
--------------------------------------------------------------------------------
 1 | import { AnyMap } from '../typings'
 2 | 
 3 | export const CURRENCY_SYMBOLS: AnyMap = {
 4 |   EUR: '\u20ac',
 5 |   GBP: '\xa3',
 6 |   USD: '
#39;,
 7 |   AUD: 'A
#39;,
 8 |   CAD: 'CA
#39;,
 9 |   CHF: 'CHF ',
10 |   NOK: 'NOK ',
11 |   SEK: 'SEK ',
12 |   DKK: 'DKK ',
13 |   MXN: 'MX
#39;,
14 |   NZD: 'NZ
#39;,
15 |   BRL: 'R
#39;
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/consts/rating.tsx:
--------------------------------------------------------------------------------
 1 | import { CrownIcon, EmotionIcon, LikeIcon, StarIcon, ThumbsUpIcon } from '../components/Icons'
 2 | import { AnyMap } from '../typings'
 3 | 
 4 | export const RATING_SHAPE_ICONS: AnyMap = {
 5 |   heart: <LikeIcon />,
 6 |   thumb_up: <ThumbsUpIcon />,
 7 |   happy: <EmotionIcon />,
 8 |   crown: <CrownIcon />,
 9 |   star: <StarIcon />
10 | }
11 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/i18n.ts:
--------------------------------------------------------------------------------
 1 | import i18n from 'i18next'
 2 | import { initReactI18next } from 'react-i18next'
 3 | 
 4 | import { locales } from './locales'
 5 | import { AnyMap } from './typings'
 6 | 
 7 | export function initI18n(fallbackLng = 'en', customLocales?: AnyMap) {
 8 |   const resources = customLocales || locales
 9 | 
10 |   i18n.use(initReactI18next).init({
11 |     lowerCaseLng: true,
12 |     supportedLngs: Object.keys(resources),
13 |     fallbackLng,
14 |     resources,
15 |     interpolation: {
16 |       escapeValue: false
17 |     },
18 |     react: {
19 |       // https://react.i18next.com/latest/trans-component#trans-props
20 |       transSupportBasicHtmlNodes: true,
21 |       transKeepBasicHtmlNodesFor: ['br', 'strong', 'b', 'i', 'a']
22 |     }
23 |   })
24 | 
25 |   return i18n
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './components/Button'
 2 | export * from './components/FlagIcon'
 3 | export * from './components/Input'
 4 | export { AutoResizeTextarea } from './components/Textarea'
 5 | export * from './components/Rate'
 6 | export * from './consts'
 7 | export * from './blocks/SuspendedMessage'
 8 | export * from './i18n'
 9 | export * from './locales'
10 | export * from './Renderer'
11 | export * from './theme'
12 | export * from './utils'
13 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/typings.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum, FormField, FormModel } from '@heyform-inc/shared-types-enums'
 2 | import type { HTMLAttributes } from 'react'
 3 | 
 4 | export interface IFormField extends FormField {
 5 |   parent?: IFormField
 6 |   isTouched?: boolean
 7 | }
 8 | 
 9 | export interface IFormModel extends FormModel {
10 |   fields: IFormField[]
11 | }
12 | 
13 | export interface IPartialFormField {
14 |   id: string
15 |   index: number
16 |   title?: string | any[]
17 |   kind: FieldKindEnum
18 |   required?: boolean
19 |   children?: IPartialFormField[]
20 | }
21 | 
22 | export type AnyMap<T = any> = Record<string, T>
23 | export type IComponentProps<E = HTMLElement> = HTMLAttributes<E>
24 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common'
2 | export * from './form'
3 | export * from './hook'
4 | export * from './lru'
5 | export * from './message'
6 | export * from './script'
7 | export { default as GlobalTimeout, Timeout } from './timeout'
8 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/utils/message.ts:
--------------------------------------------------------------------------------
 1 | export function sendMessageToParent(eventName: string) {
 2 |   window.parent?.postMessage(
 3 |     {
 4 |       source: 'HEYFORM',
 5 |       eventName
 6 |     },
 7 |     '*'
 8 |   )
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/form-renderer/src/utils/timeout.ts:
--------------------------------------------------------------------------------
 1 | import { AnyMap } from '../typings'
 2 | 
 3 | interface TimeoutProps {
 4 |   name: string
 5 |   duration: number
 6 |   callback: () => void
 7 | }
 8 | 
 9 | export class Timeout {
10 |   private readonly caches: AnyMap<number> = {}
11 | 
12 |   add({ name, duration, callback }: TimeoutProps) {
13 |     this.caches[name] = setTimeout(callback, duration)
14 |   }
15 | 
16 |   remove(name: string) {
17 |     const cache = this.caches[name]
18 | 
19 |     if (cache) {
20 |       clearTimeout(cache)
21 |       delete this.caches[name]
22 |     }
23 |   }
24 | 
25 |   clear() {
26 |     Object.keys(this.caches).forEach(key => {
27 |       this.remove(key)
28 |     })
29 |   }
30 | }
31 | 
32 | export default new Timeout()
33 | 


--------------------------------------------------------------------------------
/packages/form-renderer/tailwind.config.js:
--------------------------------------------------------------------------------
 1 | const defaultTheme = require('tailwindcss/defaultTheme')
 2 | 
 3 | module.exports = {
 4 |   content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
 5 |   theme: {
 6 |     fontFamily: {
 7 |       sans: ['Inter', ...defaultTheme.fontFamily.sans],
 8 |     },
 9 |     extend: {
10 |     }
11 |   },
12 |   variants: {
13 |     extend: {}
14 |   },
15 |   plugins: [require('tailwindcss-animate'), require('@tailwindcss/forms')]
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/form-renderer/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ESNext",
 4 | 		"module": "esnext",
 5 | 		"moduleResolution": "node",
 6 |     "lib": ["dom", "dom.iterable", "esnext"],
 7 |     "allowJs": false,
 8 |     "skipLibCheck": true,
 9 | 		"esModuleInterop": true,
10 |     "allowSyntheticDefaultImports": true,
11 |     "strict": true,
12 |     "forceConsistentCasingInFileNames": true,
13 |     "noFallthroughCasesInSwitch": true,
14 |     "resolveJsonModule": true,
15 |     "isolatedModules": true,
16 |     "noEmit": true,
17 |     "jsx": "react-jsx"
18 |   },
19 |   "include": ["src", "global-env.d.ts"]
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/form-renderer/tsup.config.js:
--------------------------------------------------------------------------------
 1 | /** @type {import('tsup').Options} */
 2 | module.exports = {
 3 | 	target: 'esnext',
 4 | 	dts: true,
 5 | 	sourcemap: true,
 6 | 	entry: ['src/index.ts'],
 7 | 	format: ['cjs', 'esm'],
 8 | 	splitting: false,
 9 | 	treeshake: true,
10 | 	clean: true,
11 | 	esbuildOptions(options) {
12 | 		options.charset = 'utf8'
13 | 	}
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/server/.eslintrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "parser": "@typescript-eslint/parser",
 3 |   "extends": [
 4 |     "plugin:@typescript-eslint/eslint-recommended",
 5 |     "plugin:@typescript-eslint/recommended",
 6 |     "prettier/@typescript-eslint",
 7 |     "plugin:prettier/recommended"
 8 |   ],
 9 |   "rules": {
10 |     "@typescript-eslint/explicit-function-return-type": "off",
11 |     "@typescript-eslint/ban-ts-ignore": "off",
12 |     "@typescript-eslint/camelcase": "off",
13 |     "@typescript-eslint/ban-ts-comment": "off",
14 |     "@typescript-eslint/ban-types": "off",
15 |     "@typescript-eslint/no-var-requires": "off",
16 |     "prettier/prettier": "error"
17 |   },
18 |   "env": {
19 |     "node": true
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/.npmrc:
--------------------------------------------------------------------------------
1 | enable-pre-post-scripts=true
2 | 


--------------------------------------------------------------------------------
/packages/server/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 100,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid",
11 |   "importOrder": ["^@heyform-inc/", "^@(decorator|graphql|guard|dto|interceptor|middleware|config|environments|controller|model|resolver|service|schedule|utils)", "^[.+/]"],
12 |   "importOrderSeparation": true,
13 |   "importOrderSortSpecifiers": true,
14 |   "importOrderGroupNamespaceSpecifiers": false,
15 |   "importOrderParserPlugins": [
16 |     "typescript",
17 |     "decorators-legacy"
18 |   ]
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 |   "collection": "@nestjs/schematics",
3 |   "sourceRoot": "src"
4 | }
5 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/account_deletion_alert.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: Your HeyForm account has been deleted
 3 | ---
 4 | 
 5 | <p>
 6 |     Your HeyForm account has been deleted. We've processed the request to delete your account.
 7 | </p>
 8 | 
 9 | <p>
10 |     All the data, including but not limited to the account, workspaces, projects, forms, and submissions, has been
11 |     permanently deleted.
12 | </p>
13 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/account_deletion_request.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: Verify your HeyForm account deletion request
 3 | ---
 4 | 
 5 | <p>We're sorry to say goodbye. You have requested to delete your HeyForm account. The verification code to proceed with the
 6 |     delete request is</p>
 7 | 
 8 | <p style="font-weight: bold">
 9 |     <code>{code}</code>
10 | </p>
11 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/email_verification_request.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: Verify your HeyForm email address
 3 | ---
 4 | 
 5 | <p>To continue setting up your HeyForm account, we need to confirm your email address. Please enter this code in your
 6 |   browser.</p>
 7 | 
 8 | <p style="font-weight: bold;">
 9 |   <code>{code}</code>
10 | </p>


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/password_change_alert.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: Your HeyForm password has been updated
3 | ---
4 | 
5 | <p>Your HeyForm password has been changed.</p>
6 | <p>
7 |   If you did not make these changes or you believe an unauthorized person has accessed your account, you should visit the dashboard immediately to reset your password.
8 | </p>


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/project_deletion_alert.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: Your HeyForm project has been deleted
 3 | ---
 4 | 
 5 | <p>
 6 |     This email is to confirm that the <span style="font-weight: bold">{projectName}</span> project has been deleted from
 7 |     <span style="font-weight: bold">{teamName}</span> workspace by <span style="font-weight: bold">{userName}</span>
 8 | </p>
 9 | 
10 | <p>All your forms and submissions associated with the <span style="font-weight: bold">{projectName}</span> project have
11 |     been deleted from the system.
12 | </p>


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/project_deletion_request.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: Verify your project deletion request
3 | ---
4 | 
5 | <p>You have requested the deletion of the <span style="font-weight: bold;">{projectName}</span> project from <span style="font-weight: bold;">{teamName}</span> workspace. The verification code to proceed with the delete request is</p>
6 | <p>
7 |     <span style="font-weight: bold;">{code}</span>
8 | </p>
9 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/schedule_account_deletion_alert.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: You have scheduled your HeyForm account for deletion
 3 | ---
 4 | 
 5 | <p>We've received the request to delete your HeyForm account. Our system will permanently delete all of your
 6 |     data, including but not limited to account, workspaces, projects, forms and submissions in 48 hours.</p>
 7 | <p>
 8 |     The deletion was requested by <strong>{fullName} ({email})</strong>.
 9 | </p>
10 | <p>
11 |     Having a second thought? Please login to your HeyForm account and select the option to <strong>Cancel scheduled
12 |         deletion</strong>. You must do this within 48 hours of the request to prevent permanent data loss.
13 | </p>
14 | <p>
15 |     NB: Your decision is final. HeyForm will not be able to recover your data after deletion.
16 | </p>


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/submission_notification.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: A new submission was received for {formName}
 3 | ---
 4 | 
 5 | <p>You have just received a new form submission. Here it goes:</p>
 6 | 
 7 | <div>
 8 |   {submission}
 9 | 
10 |     <p>
11 |         <a href="{link}">View All Submissions</a>
12 |     </p>
13 | </div>
14 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/team_deletion_alert.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: You HeyForm workspace has been deleted
3 | ---
4 | 
5 | <p>
6 |     This email is to confirm that the '{teamName}' workspace has been deleted by '{userName}'. Your workspace's projects, forms and submissions have been deleted from the system.
7 | </p>
8 | 


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/team_deletion_request.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: Verify your HeyForm workspace deletion request
3 | ---
4 | 
5 | <p>You have requested the deletion of the {teamName} HeyForm workspace. The verification code to proceed with
6 |     the delete request is</p>
7 | <p>
8 |     <span style="font-weight: bold;">{code}</span>
9 | </p>


--------------------------------------------------------------------------------
/packages/server/resources/email-templates/team_invitation.html:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: {userName} invited you to '{teamName}' workspace
 3 | ---
 4 | 
 5 | <p>{userName} has invited you to collaborate on the {teamName} workspace.</p>
 6 | <p>
 7 |   <a href="{link}">View Invitation</a>
 8 | </p>
 9 | <p>This invitation will expire in 7 days.</p>
10 | <p>
11 |   Note: This invitation was intended for {email}. If you were not
12 |   expecting this invitation, you can ignore this email.
13 | </p>


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/auth.decorator.ts:
--------------------------------------------------------------------------------
1 | import { UseGuards, applyDecorators } from '@nestjs/common'
2 | 
3 | import { AuthGuard, BrowserIdGuard } from '@guard'
4 | 
5 | export function Auth(): any {
6 |   return applyDecorators(UseGuards(BrowserIdGuard, AuthGuard))
7 | }
8 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/data-mask-options.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { SetMetadata } from '@nestjs/common'
 2 | import { CustomDecorator } from '@nestjs/common/decorators/core/set-metadata.decorator'
 3 | import { ClassTransformOptions } from 'class-transformer'
 4 | 
 5 | import { TypeFunc } from '@interceptor'
 6 | 
 7 | export const DATA_MASK_OPTIONS = 'DATA_MASK_OPTIONS'
 8 | 
 9 | export const DataMaskOptions = (
10 |   typeFunc: TypeFunc,
11 |   options?: ClassTransformOptions
12 | ): CustomDecorator<any> => {
13 |   return SetMetadata(DATA_MASK_OPTIONS, {
14 |     ...(options || {}),
15 |     typeFunc
16 |   })
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/form.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | import { UserModel } from '@model'
 7 | 
 8 | /**
 9 |  * Get req.form attached to AuthGuard (guard/permission.guard.ts)
10 |  */
11 | export const Form = createParamDecorator(
12 |   (_: any, context: ExecutionContext): UserModel => {
13 |     const ctx = GqlExecutionContext.create(context)
14 |     let { req } = ctx.getContext()
15 | 
16 |     if (helper.isEmpty(req)) {
17 |       req = context.switchToHttp().getRequest()
18 |     }
19 | 
20 |     return req.form
21 |   }
22 | )
23 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/graphql.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | export const GraphqlRequest = createParamDecorator((_: any, context: ExecutionContext) => {
 5 |   const ctx = GqlExecutionContext.create(context)
 6 |   const { req } = ctx.getContext()
 7 |   return req
 8 | })
 9 | 
10 | export const GraphqlResponse = createParamDecorator((_: any, context: ExecutionContext) => {
11 |   const ctx = GqlExecutionContext.create(context)
12 |   const { req } = ctx.getContext()
13 |   return req.res
14 | })
15 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.decorator'
2 | export * from './graphql.decorator'
3 | export * from './team.decorator'
4 | export * from './form.decorator'
5 | export * from './user.decorator'
6 | export * from './data-mask-options.decorator'
7 | export * from './project.decorator'
8 | export * from './permission.decorator'
9 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/permission.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { SetMetadata, UseGuards, applyDecorators } from '@nestjs/common'
 2 | 
 3 | import { PermissionGuard, PermissionScopeEnum } from '@guard'
 4 | 
 5 | export function TeamGuard(): any {
 6 |   return applyDecorators(SetMetadata('scope', PermissionScopeEnum.team), UseGuards(PermissionGuard))
 7 | }
 8 | 
 9 | export function ProjectGuard(): any {
10 |   return applyDecorators(
11 |     SetMetadata('scope', PermissionScopeEnum.project),
12 |     UseGuards(PermissionGuard)
13 |   )
14 | }
15 | 
16 | export function FormGuard(): any {
17 |   return applyDecorators(SetMetadata('scope', PermissionScopeEnum.form), UseGuards(PermissionGuard))
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/project.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | import { UserModel } from '@model'
 7 | 
 8 | /**
 9 |  * Get req.project attached to AuthGuard (guard/permission.guard.ts)
10 |  */
11 | export const Project = createParamDecorator(
12 |   (_: any, context: ExecutionContext): UserModel => {
13 |     const ctx = GqlExecutionContext.create(context)
14 |     let { req } = ctx.getContext()
15 | 
16 |     if (helper.isEmpty(req)) {
17 |       req = context.switchToHttp().getRequest()
18 |     }
19 | 
20 |     return req.project
21 |   }
22 | )
23 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/team.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | import { UserModel } from '@model'
 7 | 
 8 | /**
 9 |  * Get req.team attached to AuthGuard (guard/permission.guard.ts)
10 |  */
11 | export const Team = createParamDecorator(
12 |   (_: any, context: ExecutionContext): UserModel => {
13 |     const ctx = GqlExecutionContext.create(context)
14 |     let { req } = ctx.getContext()
15 | 
16 |     if (helper.isEmpty(req)) {
17 |       req = context.switchToHttp().getRequest()
18 |     }
19 | 
20 |     return req.team
21 |   }
22 | )
23 | 


--------------------------------------------------------------------------------
/packages/server/src/common/decorator/user.decorator.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | import { UserModel } from '@model'
 7 | 
 8 | /**
 9 |  * Get req.user attached to AuthGuard (guard/auth.guard.ts)
10 |  */
11 | export const User = createParamDecorator(
12 |   (_: any, context: ExecutionContext): UserModel => {
13 |     const ctx = GqlExecutionContext.create(context)
14 |     let { req } = ctx.getContext()
15 | 
16 |     if (helper.isEmpty(req)) {
17 |       req = context.switchToHttp().getRequest()
18 |     }
19 | 
20 |     return req.user
21 |   }
22 | )
23 | 


--------------------------------------------------------------------------------
/packages/server/src/common/dto/index.ts:
--------------------------------------------------------------------------------
 1 | import { Transform } from 'class-transformer'
 2 | import { IsInt, IsOptional, IsString, IsUrl } from 'class-validator'
 3 | import { APP_HOMEPAGE_URL } from '@environments'
 4 | 
 5 | export class ImageResizingDto {
 6 |   @IsUrl({
 7 |     require_protocol: true,
 8 |     host_whitelist: [
 9 |       '127.0.0.1',
10 |       'localhost',
11 |       'secure.gravatar.com',
12 |       'googleusercontent.com',
13 |       'images.unsplash.com',
14 |       'unsplash.com',
15 |       new URL(APP_HOMEPAGE_URL).hostname
16 |     ]
17 |   })
18 |   url: string
19 | 
20 |   @Transform(parseInt)
21 |   @IsInt()
22 |   @IsOptional()
23 |   w?: number
24 | 
25 |   @Transform(parseInt)
26 |   @IsInt()
27 |   @IsOptional()
28 |   h?: number
29 | }
30 | 
31 | export class ExportSubmissionsDto {
32 |   @IsString()
33 |   formId: string
34 | }
35 | 


--------------------------------------------------------------------------------
/packages/server/src/common/filter/index.ts:
--------------------------------------------------------------------------------
1 | export * from './all-exceptions.filter'
2 | 


--------------------------------------------------------------------------------
/packages/server/src/common/graphql/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './auth.graphql'
 2 | export * from './endpoint.graphql'
 3 | export * from './form.graphql'
 4 | export * from './integration.graphql'
 5 | export * from './submission.graphql'
 6 | export * from './team.graphql'
 7 | export * from './payment.graphql'
 8 | export * from './user.graphql'
 9 | export * from './app.graphql'
10 | export * from './template.graphql'
11 | export * from './project.graphql'
12 | export * from './unsplash.graphql'
13 | 


--------------------------------------------------------------------------------
/packages/server/src/common/graphql/label.graphql.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/server/src/common/graphql/label.graphql.ts


--------------------------------------------------------------------------------
/packages/server/src/common/graphql/payment.graphql.ts:
--------------------------------------------------------------------------------
 1 | import { Field, InputType, ObjectType } from '@nestjs/graphql'
 2 | 
 3 | import { FormDetailInput } from './form.graphql'
 4 | 
 5 | @InputType()
 6 | export class ConnectStripeInput extends FormDetailInput {
 7 |   @Field()
 8 |   state: string
 9 | 
10 |   @Field()
11 |   code: string
12 | }
13 | 
14 | @ObjectType()
15 | export class ConnectStripeType {
16 |   @Field()
17 |   accountId: string
18 | 
19 |   @Field()
20 |   email: string
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/src/common/graphql/unsplash.graphql.ts:
--------------------------------------------------------------------------------
 1 | import { Field, InputType, ObjectType } from '@nestjs/graphql'
 2 | 
 3 | @InputType()
 4 | export class UnsplashSearchInput {
 5 |   @Field({ nullable: true })
 6 |   keyword?: string
 7 | 
 8 |   @Field({ nullable: true })
 9 |   page?: number
10 | }
11 | 
12 | @ObjectType()
13 | export class UnsplashImageType {
14 |   @Field()
15 |   id: string
16 | 
17 |   @Field()
18 |   url: string
19 | 
20 |   @Field()
21 |   thumbUrl: string
22 | 
23 |   @Field({ nullable: true })
24 |   downloadUrl?: string
25 | 
26 |   @Field()
27 |   author: string
28 | 
29 |   @Field()
30 |   authorUrl: string
31 | }
32 | 
33 | @InputType()
34 | export class UnsplashTrackDownloadInput {
35 |   @Field()
36 |   downloadUrl: string
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/server/src/common/guard/endpoint-anonymous-id.guard.ts:
--------------------------------------------------------------------------------
 1 | import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | @Injectable()
 7 | export class EndpointAnonymousIdGuard implements CanActivate {
 8 |   async canActivate(context: ExecutionContext): Promise<boolean> {
 9 |     const ctx = GqlExecutionContext.create(context)
10 |     const { req } = ctx.getContext()
11 |     const anonymousId = req.get('x-anonymous-id')
12 | 
13 |     if (helper.isEmpty(anonymousId)) {
14 |       throw new ForbiddenException('Forbidden request error')
15 |     }
16 | 
17 |     return true
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/src/common/guard/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth.guard'
2 | export * from './browser-id.guard'
3 | export * from './endpoint-anonymous-id.guard'
4 | export * from './role.guard'
5 | export * from './permission.guard'
6 | 


--------------------------------------------------------------------------------
/packages/server/src/common/interceptor/index.ts:
--------------------------------------------------------------------------------
1 | export * from './data-mask.interceptor'
2 | 


--------------------------------------------------------------------------------
/packages/server/src/common/middleware/form-body.middleware.ts:
--------------------------------------------------------------------------------
 1 | import { Injectable, NestMiddleware } from '@nestjs/common'
 2 | import * as bodyParser from 'body-parser'
 3 | import { Request, Response } from 'express'
 4 | 
 5 | @Injectable()
 6 | export class FormBodyMiddleware implements NestMiddleware {
 7 |   use(req: Request, res: Response, next: () => any) {
 8 |     bodyParser.urlencoded({
 9 |       limit: '1mb',
10 |       extended: true
11 |     })(req, res, next)
12 |   }
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/server/src/common/middleware/index.ts:
--------------------------------------------------------------------------------
1 | export * from './form-body.middleware'
2 | export * from './json-body.middleware'
3 | export * from './raw-body.middleware'
4 | 


--------------------------------------------------------------------------------
/packages/server/src/common/middleware/json-body.middleware.ts:
--------------------------------------------------------------------------------
 1 | import { Injectable, NestMiddleware } from '@nestjs/common'
 2 | import * as bodyParser from 'body-parser'
 3 | import { Request, Response } from 'express'
 4 | 
 5 | @Injectable()
 6 | export class JsonBodyMiddleware implements NestMiddleware {
 7 |   use(req: Request, res: Response, next: () => any) {
 8 |     bodyParser.json({
 9 |       limit: '1mb'
10 |     })(req, res, next)
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/server/src/common/middleware/raw-body.middleware.ts:
--------------------------------------------------------------------------------
 1 | import { Injectable, NestMiddleware } from '@nestjs/common'
 2 | import * as bodyParser from 'body-parser'
 3 | import { Request, Response } from 'express'
 4 | 
 5 | @Injectable()
 6 | export class RawBodyMiddleware implements NestMiddleware {
 7 |   use(req: Request, res: Response, next: () => any) {
 8 |     bodyParser.raw({ type: '*/*' })(req, res, next)
 9 |   }
10 | }
11 | 


--------------------------------------------------------------------------------
/packages/server/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './bull'
2 | export * from './graphql'
3 | export * from './mongo'
4 | export * from './redis'
5 | export * from './cookie'
6 | export * from './smtp'
7 | export * from './upload'
8 | 


--------------------------------------------------------------------------------
/packages/server/src/config/redis/index.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   RedisModuleOptions,
 3 |   RedisModuleOptionsFactory
 4 | } from '@svtslv/nestjs-ioredis/dist/redis.interfaces'
 5 | 
 6 | import { REDIS_DB, REDIS_HOST, REDIS_PASSWORD, REDIS_PORT } from '@environments'
 7 | 
 8 | export class RedisService implements RedisModuleOptionsFactory {
 9 |   createRedisModuleOptions(): Promise<RedisModuleOptions> | RedisModuleOptions {
10 |     return {
11 |       config: {
12 |         host: REDIS_HOST,
13 |         port: REDIS_PORT,
14 |         password: REDIS_PASSWORD,
15 |         db: REDIS_DB
16 |       }
17 |     }
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/src/config/smtp/index.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SMTP_HOST,
 3 |   SMTP_PASSWORD,
 4 |   SMTP_PORT,
 5 |   SMTP_USER,
 6 |   SMTP_SECURE,
 7 |   SMTP_IGNORE_CERT,
 8 |   SMTP_SERVERNAME
 9 | } from '@environments'
10 | import { SmtpOptions } from '@utils'
11 | 
12 | export const SmtpOptionsFactory = (): SmtpOptions => ({
13 |   host: SMTP_HOST,
14 |   port: SMTP_PORT,
15 |   user: SMTP_USER,
16 |   password: SMTP_PASSWORD,
17 |   secure: SMTP_SECURE,
18 |   servername: SMTP_SERVERNAME,
19 |   ignoreCert: SMTP_IGNORE_CERT,
20 |   pool: true,
21 |   logger: false
22 | })
23 | 


--------------------------------------------------------------------------------
/packages/server/src/controller/connect-stripe.controller.ts:
--------------------------------------------------------------------------------
 1 | import { Controller, Get, Query, Res } from '@nestjs/common'
 2 | import { Response } from 'express'
 3 | 
 4 | @Controller()
 5 | export class ConnectStripeController {
 6 |   @Get('/connect/stripe/callback')
 7 |   async index(@Query() query: Record<string, string>, @Res() res: Response) {
 8 |     return res.render('connect-stripe', {
 9 |       rendererData: query
10 |     })
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/server/src/controller/form.controller.ts:
--------------------------------------------------------------------------------
 1 | import { Controller, Get, Res } from '@nestjs/common'
 2 | import { Response } from 'express'
 3 | 
 4 | @Controller()
 5 | export class FormController {
 6 |   @Get('/form/*')
 7 |   index(@Res() res: Response) {
 8 |     return res.render('index', {
 9 |       rendererData: {}
10 |     })
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/server/src/controller/health.controller.ts:
--------------------------------------------------------------------------------
 1 | import { Controller, Get } from '@nestjs/common'
 2 | 
 3 | @Controller()
 4 | export class HealthController {
 5 |   @Get('/health')
 6 |   index() {
 7 |     return 'OK'
 8 |   }
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/server/src/model/form-analytic.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | @Schema({
 5 |   timestamps: true
 6 | })
 7 | export class FormAnalyticModel extends Document {
 8 |   @Prop({ required: true, index: true })
 9 |   formId: string
10 | 
11 |   @Prop({ required: true })
12 |   totalVisits: number
13 | }
14 | 
15 | export const FormAnalyticSchema = SchemaFactory.createForClass(FormAnalyticModel)
16 | 


--------------------------------------------------------------------------------
/packages/server/src/model/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './app.model'
 2 | export * from './form.model'
 3 | export * from './form-analytic.model'
 4 | export * from './form-report.model'
 5 | export * from './integration.model'
 6 | export * from './integration-record.model'
 7 | export * from './project.model'
 8 | export * from './project-group.model'
 9 | export * from './project-member.model'
10 | export * from './submission.model'
11 | export * from './submission-ip-limit.model'
12 | export * from './team.model'
13 | export * from './team-activity.model'
14 | export * from './team-invitation.model'
15 | export * from './team-member.model'
16 | export * from './template.model'
17 | export * from './user.model'
18 | export * from './user-social-account.model'
19 | 


--------------------------------------------------------------------------------
/packages/server/src/model/integration-record.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | 
 3 | import { IntegrationModel } from './integration.model'
 4 | 
 5 | @Schema({
 6 |   timestamps: true
 7 | })
 8 | export class IntegrationRecordModel extends IntegrationModel {
 9 |   @Prop({ required: true })
10 |   integrationId: string
11 | 
12 |   @Prop({ type: Map })
13 |   response?: Record<string, any>
14 | }
15 | 
16 | export const IntegrationRecordSchema = SchemaFactory.createForClass(IntegrationRecordModel)
17 | 


--------------------------------------------------------------------------------
/packages/server/src/model/project-group.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | @Schema({
 5 |   timestamps: true
 6 | })
 7 | export class ProjectGroupModel extends Document {
 8 |   @Prop({ required: true, index: true })
 9 |   projectId: string
10 | 
11 |   @Prop({ required: true, index: true })
12 |   groupId: string
13 | }
14 | 
15 | export const ProjectGroupSchema = SchemaFactory.createForClass(ProjectGroupModel)
16 | 
17 | ProjectGroupSchema.index({ projectId: 1, groupId: 1 }, { unique: true })
18 | 


--------------------------------------------------------------------------------
/packages/server/src/model/project-member.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | @Schema()
 5 | export class ProjectMemberModel extends Document {
 6 |   @Prop({ required: true, index: true })
 7 |   projectId: string
 8 | 
 9 |   @Prop({ required: true, index: true })
10 |   memberId: string
11 | }
12 | 
13 | export const ProjectMemberSchema = SchemaFactory.createForClass(ProjectMemberModel)
14 | 
15 | ProjectMemberSchema.index({ projectId: 1, memberId: 1 }, { unique: true })
16 | 


--------------------------------------------------------------------------------
/packages/server/src/model/submission-ip-limit.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | @Schema({
 5 |   timestamps: true
 6 | })
 7 | export class SubmissionIpLimitModel extends Document {
 8 |   @Prop({ required: true })
 9 |   formId: string
10 | 
11 |   @Prop({ required: true })
12 |   ip: string
13 | 
14 |   @Prop({ required: true })
15 |   count: number
16 | 
17 |   @Prop({ required: true, index: true })
18 |   expiredAt: number
19 | }
20 | 
21 | export const SubmissionIpLimitSchema = SchemaFactory.createForClass(SubmissionIpLimitModel)
22 | 
23 | SubmissionIpLimitSchema.index({ formId: 1, ip: 1 }, { unique: true })
24 | 


--------------------------------------------------------------------------------
/packages/server/src/model/team-invitation.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | @Schema({
 5 |   timestamps: true
 6 | })
 7 | export class TeamInvitationModel extends Document {
 8 |   @Prop({ required: true })
 9 |   teamId: string
10 | 
11 |   @Prop({ required: true })
12 |   email: string
13 | 
14 |   @Prop({ required: true })
15 |   expireAt: number
16 | }
17 | 
18 | export const TeamInvitationSchema = SchemaFactory.createForClass(TeamInvitationModel)
19 | 
20 | // Unique constraint on name and lang
21 | TeamInvitationSchema.index({ teamId: 1, email: 1 }, { unique: true })
22 | 


--------------------------------------------------------------------------------
/packages/server/src/model/team-member.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | export enum TeamRoleEnum {
 5 |   OWNER,
 6 |   ADMIN,
 7 |   COLLABORATOR,
 8 |   MEMBER
 9 | }
10 | 
11 | @Schema()
12 | export class TeamMemberModel extends Document {
13 |   @Prop({ required: true, index: true })
14 |   teamId: string
15 | 
16 |   @Prop({ required: true, index: true })
17 |   memberId: string
18 | 
19 |   @Prop({ type: Number, required: true, enum: Object.values(TeamRoleEnum) })
20 |   role: TeamRoleEnum
21 | 
22 |   @Prop()
23 |   lastSeenAt?: number
24 | }
25 | 
26 | export const TeamMemberSchema = SchemaFactory.createForClass(TeamMemberModel)
27 | 
28 | TeamMemberSchema.index({ teamId: 1, memberId: 1 }, { unique: true })
29 | 


--------------------------------------------------------------------------------
/packages/server/src/model/user-social-account.model.ts:
--------------------------------------------------------------------------------
 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
 2 | import { Document } from 'mongoose'
 3 | 
 4 | import { SocialLoginTypeEnum } from '@heyform-inc/shared-types-enums'
 5 | 
 6 | @Schema({
 7 |   timestamps: true
 8 | })
 9 | export class UserSocialAccountModel extends Document {
10 |   @Prop({
11 |     type: String,
12 |     required: true
13 |   })
14 |   kind: SocialLoginTypeEnum
15 | 
16 |   @Prop({ required: true })
17 |   userId: string
18 | 
19 |   @Prop({ required: true })
20 |   openId: string
21 | }
22 | 
23 | export const UserSocialAccountSchema = SchemaFactory.createForClass(UserSocialAccountModel)
24 | 
25 | // Unique constraint
26 | UserSocialAccountSchema.index({ kind: 1, openId: 1 }, { unique: true })
27 | UserSocialAccountSchema.index({ kind: 1, userId: 1 }, { unique: true })
28 | 


--------------------------------------------------------------------------------
/packages/server/src/queue/form-report.queue.ts:
--------------------------------------------------------------------------------
 1 | import { Process, Processor } from '@nestjs/bull'
 2 | import { Job } from 'bull'
 3 | 
 4 | import { FormReportService } from '@service'
 5 | 
 6 | import { BaseQueue } from './base.queue'
 7 | 
 8 | interface FormReportQueueJob {
 9 |   formId: string
10 | }
11 | 
12 | @Processor('FormReportQueue')
13 | export class FormReportQueue extends BaseQueue {
14 |   constructor(private readonly formReportService: FormReportService) {
15 |     super()
16 |   }
17 | 
18 |   @Process()
19 |   async generateReport(job: Job<FormReportQueueJob>): Promise<any> {
20 |     const { formId } = job.data
21 |     await this.formReportService.generate(formId)
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/packages/server/src/queue/index.ts:
--------------------------------------------------------------------------------
 1 | import { BullModule } from '@nestjs/bull'
 2 | 
 3 | import { BullOptionsFactory } from '@config'
 4 | 
 5 | import { FormReportQueue } from './form-report.queue'
 6 | import { MailQueue } from './mail.queue'
 7 | import { IntegrationEmailQueue } from './integration-email.queue'
 8 | import { IntegrationWebhookQueue } from './integration-webhook.queue'
 9 | import { TranslateFormQueue } from './translate-form.queue'
10 | 
11 | export const QueueProviders = {
12 |   FormReportQueue,
13 |   MailQueue,
14 |   IntegrationEmailQueue,
15 |   IntegrationWebhookQueue,
16 |   TranslateFormQueue
17 | }
18 | 
19 | export const QueueModules = Object.keys(QueueProviders).map(queueName => {
20 |   return BullModule.registerQueueAsync({
21 |     name: queueName,
22 |     useFactory: BullOptionsFactory
23 |   })
24 | })
25 | 


--------------------------------------------------------------------------------
/packages/server/src/queue/mail.queue.ts:
--------------------------------------------------------------------------------
 1 | import { Process, Processor } from '@nestjs/bull'
 2 | import { Job } from 'bull'
 3 | 
 4 | import { SmtpOptionsFactory } from '@config'
 5 | import { SmtpMessage, SmtpOptions, smtpSendMail } from '@utils'
 6 | 
 7 | import { BaseQueue } from './base.queue'
 8 | 
 9 | export interface MailQueueJob {
10 |   data: SmtpMessage
11 | }
12 | 
13 | @Processor('MailQueue')
14 | export class MailQueue extends BaseQueue {
15 |   private readonly options!: SmtpOptions
16 | 
17 |   constructor() {
18 |     super()
19 |     this.options = SmtpOptionsFactory()
20 |   }
21 | 
22 |   @Process()
23 |   async sendMail(job: Job<MailQueueJob>) {
24 |     return smtpSendMail(this.options, job.data.data)
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/app/apps.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { AppType } from '@graphql'
 4 | import { AppModel } from '@model'
 5 | import { AppService } from '@service'
 6 | 
 7 | @Resolver()
 8 | export class AppsResolver {
 9 |   constructor(private readonly appService: AppService) {}
10 | 
11 |   @Query(returns => [AppType])
12 |   async apps(): Promise<AppModel[]> {
13 |     return this.appService.findAll()
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/create-form-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { FormField } from '@heyform-inc/shared-types-enums'
 4 | 
 5 | import { Auth, FormGuard } from '@decorator'
 6 | import { CreateFormFieldInput } from '@graphql'
 7 | import { FormService } from '@service'
 8 | 
 9 | @Resolver()
10 | @Auth()
11 | export class CreateFormFieldResolver {
12 |   constructor(private readonly formService: FormService) {}
13 | 
14 |   @Mutation(returns => Boolean)
15 |   @FormGuard()
16 |   async createFormField(@Args('input') input: CreateFormFieldInput): Promise<boolean> {
17 |     return this.formService.createField(input.formId, input.field as FormField)
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/create-form-hidden-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | import { Auth, FormGuard } from '@decorator'
 3 | import { FormService } from '@service'
 4 | import { CreateHiddenFieldInput } from '@graphql'
 5 | 
 6 | @Resolver()
 7 | @Auth()
 8 | export class CreateFormHiddenFieldResolver {
 9 |   constructor(private readonly formService: FormService) {}
10 | 
11 |   @Mutation(returns => Boolean)
12 |   @FormGuard()
13 |   async createFormHiddenField(@Args('input') input: CreateHiddenFieldInput): Promise<boolean> {
14 |     await this.formService.update(input.formId, {
15 |       $push: {
16 |         hiddenFields: {
17 |           id: input.fieldId,
18 |           name: input.fieldName
19 |         }
20 |       }
21 |     })
22 | 
23 |     return true
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/delete-form-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { DeleteFormFieldInput } from '@graphql'
 5 | import { FormService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class DeleteFormFieldResolver {
10 |   constructor(private readonly formService: FormService) {}
11 | 
12 |   @Mutation(returns => Boolean)
13 |   @FormGuard()
14 |   async deleteFormField(@Args('input') input: DeleteFormFieldInput): Promise<boolean> {
15 |     return this.formService.deleteField(input.formId, input.fieldId)
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/delete-form-hidden-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | import { Auth, FormGuard } from '@decorator'
 3 | import { FormService } from '@service'
 4 | import { DeleteHiddenFieldInput } from '@graphql'
 5 | 
 6 | @Resolver()
 7 | @Auth()
 8 | export class DeleteFormHiddenFieldResolver {
 9 |   constructor(private readonly formService: FormService) {}
10 | 
11 |   @Mutation(returns => Boolean)
12 |   @FormGuard()
13 |   async deleteFormHiddenField(@Args('input') input: DeleteHiddenFieldInput): Promise<boolean> {
14 |     await this.formService.update(input.formId, {
15 |       $pull: {
16 |         hiddenFields: {
17 |           id: input.fieldId
18 |         }
19 |       }
20 |     })
21 | 
22 |     return true
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/delete-form.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { FormDetailInput } from '@graphql'
 5 | import { FormService, SubmissionService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class DeleteFormResolver {
10 |   constructor(
11 |     private readonly formService: FormService,
12 |     private readonly submissionService: SubmissionService
13 |   ) {}
14 | 
15 |   /**
16 |    * Delete form
17 |    *
18 |    * @param input
19 |    */
20 |   @Mutation(returns => Boolean)
21 |   @FormGuard()
22 |   async deleteForm(@Args('input') input: FormDetailInput): Promise<boolean> {
23 |     await this.formService.delete(input.formId)
24 |     await this.submissionService.deleteAll(input.formId)
25 |     return true
26 |   }
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/form-integrations.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { FormDetailInput, FormIntegrationType } from '@graphql'
 5 | import { IntegrationModel } from '@model'
 6 | import { IntegrationService } from '@service'
 7 | 
 8 | @Resolver()
 9 | @Auth()
10 | export class FormIntegrationsResolver {
11 |   constructor(private readonly integrationService: IntegrationService) {}
12 | 
13 |   @Query(returns => [FormIntegrationType])
14 |   @FormGuard()
15 |   async formIntegrations(@Args('input') input: FormDetailInput): Promise<IntegrationModel[]> {
16 |     return this.integrationService.findAllInForm(input.formId)
17 |   }
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/restore-form.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { FormStatusEnum } from '@heyform-inc/shared-types-enums'
 4 | 
 5 | import { Auth, Form, FormGuard } from '@decorator'
 6 | import { FormDetailInput } from '@graphql'
 7 | import { FormModel } from '@model'
 8 | import { FormService } from '@service'
 9 | 
10 | @Resolver()
11 | @Auth()
12 | export class RestoreFormResolver {
13 |   constructor(private readonly formService: FormService) {}
14 | 
15 |   @Mutation(returns => Boolean)
16 |   @FormGuard()
17 |   async restoreForm(
18 |     @Form() form: FormModel,
19 |     @Args('input') input: FormDetailInput
20 |   ): Promise<boolean> {
21 |     return this.formService.update(input.formId, {
22 |       retentionAt: -1,
23 |       status: FormStatusEnum.NORMAL
24 |     })
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/update-form-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { UpdateFormFieldInput } from '@graphql'
 5 | import { FormService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class UpdateFormFieldResolver {
10 |   constructor(private readonly formService: FormService) {}
11 | 
12 |   @Mutation(returns => Boolean)
13 |   @FormGuard()
14 |   async updateFormField(@Args('input') input: UpdateFormFieldInput): Promise<boolean> {
15 |     return this.formService.updateField(input)
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/update-form-hidden-field.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | import { Auth, FormGuard } from '@decorator'
 3 | import { FormService } from '@service'
 4 | import { CreateHiddenFieldInput } from '@graphql'
 5 | 
 6 | @Resolver()
 7 | @Auth()
 8 | export class UpdateFormHiddenFieldResolver {
 9 |   constructor(private readonly formService: FormService) {}
10 | 
11 |   @Mutation(returns => Boolean)
12 |   @FormGuard()
13 |   async updateFormHiddenField(@Args('input') input: CreateHiddenFieldInput): Promise<boolean> {
14 |     await this.formService.update(
15 |       input.formId,
16 |       {
17 |         $set: {
18 |           'hiddenFields.$.name': input.fieldName
19 |         }
20 |       },
21 |       {
22 |         'hiddenFields.id': input.fieldId
23 |       }
24 |     )
25 | 
26 |     return true
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/update-form-logics.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { UpdateFormLogicsInput } from '@graphql'
 5 | import { FormService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class UpdateFormLogicsResolver {
10 |   constructor(private readonly formService: FormService) {}
11 | 
12 |   /**
13 |    * Update form logics
14 |    */
15 |   @Mutation(returns => Boolean)
16 |   @FormGuard()
17 |   async updateFormLogics(@Args('input') input: UpdateFormLogicsInput): Promise<boolean> {
18 |     return this.formService.update(input.formId, {
19 |       logics: input.logics
20 |     })
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/update-form-schemas.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { timestamp } from '@heyform-inc/utils'
 4 | 
 5 | import { Auth, FormGuard } from '@decorator'
 6 | import { UpdateFormSchemasInput } from '@graphql'
 7 | import { FormService } from '@service'
 8 | 
 9 | @Resolver()
10 | @Auth()
11 | export class UpdateFormSchemasResolver {
12 |   constructor(private readonly formService: FormService) {}
13 | 
14 |   @Mutation(returns => Boolean)
15 |   @FormGuard()
16 |   async updateFormSchemas(@Args('input') input: UpdateFormSchemasInput): Promise<boolean> {
17 |     return this.formService.update(input.formId, {
18 |       fields: input.fields,
19 |       fieldUpdateAt: timestamp()
20 |     })
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/form/update-form-variables.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { UpdateFormVariablesInput } from '@graphql'
 5 | import { FormService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class UpdateFormVariablesResolver {
10 |   constructor(private readonly formService: FormService) {}
11 | 
12 |   /**
13 |    * Update form variables
14 |    */
15 |   @Mutation(returns => Boolean)
16 |   @FormGuard()
17 |   async updateFormVariables(@Args('input') input: UpdateFormVariablesInput): Promise<boolean> {
18 |     return this.formService.update(input.formId, {
19 |       variables: input.variables
20 |     })
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/integration/delete-integration-settings.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { ThirdPartyInput } from '@graphql'
 5 | import { IntegrationService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class DeleteIntegrationSettingsResolver {
10 |   constructor(private readonly integrationService: IntegrationService) {}
11 | 
12 |   @Mutation(returns => Boolean)
13 |   @FormGuard()
14 |   async deleteIntegrationSettings(
15 |     @Args('input')
16 |     input: ThirdPartyInput
17 |   ): Promise<boolean> {
18 |     return this.integrationService.delete(input.formId, input.appId)
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/payment/revoke-stripe-account.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { FormDetailInput } from '@graphql'
 5 | import { FormService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class RevokeStripeAccountResolver {
10 |   constructor(private readonly formService: FormService) {}
11 | 
12 |   @Mutation(returns => Boolean)
13 |   @FormGuard()
14 |   async revokeStripeAccount(@Args('input') input: FormDetailInput): Promise<boolean> {
15 |     return this.formService.update(input.formId, {
16 |       stripeAccount: undefined
17 |     })
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/project/leave-project.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, ProjectGuard, User } from '@decorator'
 4 | import { ProjectDetailInput } from '@graphql'
 5 | import { UserModel } from '@model'
 6 | import { ProjectService } from '@service'
 7 | 
 8 | @Resolver()
 9 | @Auth()
10 | export class LeaveProjectResolver {
11 |   constructor(private readonly projectService: ProjectService) {}
12 | 
13 |   @ProjectGuard()
14 |   @Mutation(returns => Boolean)
15 |   async leaveProject(
16 |     @User() user: UserModel,
17 |     @Args('input') input: ProjectDetailInput
18 |   ): Promise<boolean> {
19 |     return this.projectService.deleteMember(input.projectId, user.id)
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/project/projects.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, TeamGuard, User } from '@decorator'
 4 | import { ProjectType, TeamDetailInput } from '@graphql'
 5 | import { ProjectModel, UserModel } from '@model'
 6 | import { ProjectService } from '@service'
 7 | 
 8 | @Resolver()
 9 | @Auth()
10 | export class ProjectsResolver {
11 |   constructor(private readonly projectService: ProjectService) {}
12 | 
13 |   @TeamGuard()
14 |   @Query(returns => [ProjectType])
15 |   async projects(
16 |     @User() user: UserModel,
17 |     @Args('input') input: TeamDetailInput
18 |   ): Promise<ProjectModel[]> {
19 |     const projectIds = await this.projectService.findProjectsByMemberId(user.id)
20 | 
21 |     return this.projectService.findByIds(projectIds, {
22 |       teamId: input.teamId
23 |     })
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/project/rename-project.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, ProjectGuard, Team } from '@decorator'
 4 | import { RenameProjectInput } from '@graphql'
 5 | import { TeamModel } from '@model'
 6 | import { ProjectService } from '@service'
 7 | 
 8 | @Resolver()
 9 | @Auth()
10 | export class RenameProjectResolver {
11 |   constructor(private readonly projectService: ProjectService) {}
12 | 
13 |   @ProjectGuard()
14 |   @Mutation(returns => Boolean)
15 |   async renameProject(
16 |     @Team() team: TeamModel,
17 |     @Args('input') input: RenameProjectInput
18 |   ): Promise<boolean> {
19 |     return this.projectService.update(input.projectId, {
20 |       name: input.name
21 |     })
22 |   }
23 | }
24 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/submission/delete-submission.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, FormGuard } from '@decorator'
 4 | import { DeleteSubmissionInput } from '@graphql'
 5 | import { SubmissionService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class DeleteSubmissionResolver {
10 |   constructor(private readonly submissionService: SubmissionService) {}
11 | 
12 |   /**
13 |    * Delete submissions
14 |    *
15 |    * @param input
16 |    */
17 |   @Mutation(returns => Boolean)
18 |   @FormGuard()
19 |   async deleteSubmissions(@Args('input') input: DeleteSubmissionInput): Promise<boolean> {
20 |     return this.submissionService.deleteByIds(input.formId, input.submissionIds)
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/template/template-detail.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { helper } from '@heyform-inc/utils'
 4 | 
 5 | import { TemplateDetailInput, TemplateDetailType } from '@graphql'
 6 | import { TemplateModel } from '@model'
 7 | import { TemplateService } from '@service'
 8 | 
 9 | @Resolver()
10 | export class TemplateDetailResolver {
11 |   constructor(private readonly templateService: TemplateService) {}
12 | 
13 |   @Query(returns => TemplateDetailType)
14 |   async templateDetail(@Args('input') input: TemplateDetailInput): Promise<TemplateModel> {
15 |     if (helper.isValid(input.templateSlug)) {
16 |       return this.templateService.findBySlug(input.templateSlug)
17 |     } else {
18 |       return this.templateService.findById(input.templateId)
19 |     }
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/template/templates.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { TemplateType, TemplatesInput } from '@graphql'
 4 | import { TemplateModel } from '@model'
 5 | import { TemplateService } from '@service'
 6 | 
 7 | @Resolver()
 8 | export class TemplatesResolver {
 9 |   constructor(private readonly templateService: TemplateService) {}
10 | 
11 |   @Query(returns => [TemplateType])
12 |   async templates(@Args('input') input: TemplatesInput): Promise<TemplateModel[]> {
13 |     return this.templateService.findAll(input.keyword, input.limit)
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/unsplash/unsplash-track-download.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Args, Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { helper } from '@heyform-inc/utils'
 4 | 
 5 | import { Auth } from '@decorator'
 6 | import { UNSPLASH_CLIENT_ID } from '@environments'
 7 | import { UnsplashTrackDownloadInput } from '@graphql'
 8 | import { Unsplash } from '@utils'
 9 | 
10 | @Resolver()
11 | @Auth()
12 | export class UnsplashTrackDownloadResolver {
13 |   @Mutation(returns => Boolean)
14 |   async unsplashTrackDownload(@Args('input') input: UnsplashTrackDownloadInput): Promise<boolean> {
15 |     const unsplash = Unsplash.init({
16 |       clientId: UNSPLASH_CLIENT_ID
17 |     })
18 |     return helper.isValid(await unsplash.trackDownload(input.downloadUrl))
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/user/cancel-user-deletion.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Mutation, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, User } from '@decorator'
 4 | import { UserModel } from '@model'
 5 | import { RedisService, UserService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class CancelUserDeletionResolver {
10 |   constructor(
11 |     private readonly userService: UserService,
12 |     private readonly redisService: RedisService
13 |   ) {}
14 | 
15 |   @Mutation(returns => Boolean)
16 |   async cancelUserDeletion(@User() user: UserModel): Promise<boolean> {
17 |     const key = `UserDeletion:${user.id}`
18 | 
19 |     const result = await this.userService.update(user.id, {
20 |       isDeletionScheduled: false,
21 |       deletionScheduledAt: 0
22 |     })
23 | 
24 |     await this.redisService.del(key)
25 | 
26 |     return result
27 |   }
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/server/src/resolver/user/user-deletion-code.resolver.ts:
--------------------------------------------------------------------------------
 1 | import { Query, Resolver } from '@nestjs/graphql'
 2 | 
 3 | import { Auth, User } from '@decorator'
 4 | import { UserModel } from '@model'
 5 | import { AuthService, MailService } from '@service'
 6 | 
 7 | @Resolver()
 8 | @Auth()
 9 | export class UserDeletionCodeResolver {
10 |   constructor(
11 |     private readonly mailService: MailService,
12 |     private readonly authService: AuthService
13 |   ) {}
14 | 
15 |   @Query(returns => Boolean)
16 |   async userDeletionCode(@User() user: UserModel): Promise<boolean> {
17 |     const key = `user_deletion:${user.id}`
18 |     const code = await this.authService.getVerificationCode(key)
19 | 
20 |     await this.mailService.accountDeletionRequest(user.email, code)
21 | 
22 |     return true
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/server/src/schedule/delete-form-in-trash.schedule.ts:
--------------------------------------------------------------------------------
 1 | import { Process, Processor } from '@nestjs/bull'
 2 | 
 3 | import { FormService } from '@service'
 4 | 
 5 | import { BaseQueue } from '../queue/base.queue'
 6 | 
 7 | @Processor('DeleteFormInTrashSchedule')
 8 | export class DeleteFormInTrashSchedule extends BaseQueue {
 9 |   constructor(private readonly formService: FormService) {
10 |     super()
11 |   }
12 | 
13 |   @Process()
14 |   async deleteFormInTrash(): Promise<any> {
15 |     const forms = await this.formService.findAllInTrash()
16 | 
17 |     if (forms.length > 0) {
18 |       await this.formService.delete(forms.map(row => row.id))
19 |     }
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/src/schedule/index.ts:
--------------------------------------------------------------------------------
 1 | import { BullModule } from '@nestjs/bull'
 2 | 
 3 | import { BullOptionsFactory } from '@config'
 4 | 
 5 | import { DeleteFormInTrashSchedule } from './delete-form-in-trash.schedule'
 6 | import { DeleteUserAccountSchedule } from './delete-user-account.schedule'
 7 | import { ResetInviteCodeSchedule } from './reset-invite-code.schedule'
 8 | 
 9 | export const ScheduleProviders = {
10 |   DeleteFormInTrashSchedule,
11 |   ResetInviteCodeSchedule,
12 |   DeleteUserAccountSchedule
13 | }
14 | 
15 | export const ScheduleModules = Object.keys(ScheduleProviders).map(scheduleName => {
16 |   return BullModule.registerQueueAsync({
17 |     name: scheduleName,
18 |     useFactory: BullOptionsFactory
19 |   })
20 | })
21 | 


--------------------------------------------------------------------------------
/packages/server/src/service/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './auth.service'
 2 | export * from './endpoint.service'
 3 | export * from './form.service'
 4 | export * from './form-analytic.service'
 5 | export * from './form-report.service'
 6 | export * from './mail.service'
 7 | export * from './payment.service'
 8 | export * from './redis.service'
 9 | export * from './schedule.service'
10 | export * from './social-login.service'
11 | export * from './submission.service'
12 | export * from './submission-ip-limit.service'
13 | export * from './team.service'
14 | export * from './user.service'
15 | export * from './integration.service'
16 | export * from './export-file.service'
17 | export * from './app.service'
18 | export * from './template.service'
19 | export * from './project.service'
20 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/anti-bot/akismet.ts:
--------------------------------------------------------------------------------
 1 | // @ts-ignore
 2 | import { AkismetClient } from 'akismet-api'
 3 | 
 4 | interface VerifySpamOptions {
 5 |   key: string
 6 |   url: string
 7 |   ip?: string
 8 |   userAgent?: string
 9 | }
10 | 
11 | export async function verifySpam(content: string, options: VerifySpamOptions): Promise<boolean> {
12 |   const client = new AkismetClient({
13 |     key: options.key,
14 |     blog: options.url
15 |   })
16 | 
17 |   return await client.checkSpam({
18 |     type: 'contact-form',
19 |     ip: options.ip,
20 |     useragent: options.userAgent,
21 |     content
22 |   })
23 | }
24 | 
25 | export default {
26 |   verifySpam
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/anti-bot/index.ts:
--------------------------------------------------------------------------------
1 | export { default as akismet } from './akismet'
2 | export { default as recaptcha } from './recaptcha'
3 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/anti-bot/recaptcha.ts:
--------------------------------------------------------------------------------
 1 | import { request } from '../social-login/utils'
 2 | 
 3 | interface RecaptchaOptions {
 4 |   secret: string
 5 | }
 6 | 
 7 | export async function verifyRecaptcha(token: string, options: RecaptchaOptions): Promise<any> {
 8 |   const result = await request({
 9 |     method: 'POST',
10 |     url: 'https://www.google.com/recaptcha/api/siteverify',
11 |     data: {
12 |       secret: options.secret,
13 |       response: token
14 |     }
15 |   })
16 |   return result.data?.success
17 | }
18 | 
19 | export default {
20 |   verifyRecaptcha
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/decorators/device-id.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | import 'reflect-metadata'
 4 | 
 5 | export const GqlBrowserId = createParamDecorator((_: any, context: ExecutionContext): string => {
 6 |   const ctx = GqlExecutionContext.create(context)
 7 |   const { req } = ctx.getContext()
 8 |   return req.get('x-browser-Id')
 9 | })
10 | 
11 | export const HttpBrowserId = createParamDecorator((_: any, ctx: ExecutionContext): string => {
12 |   const req = ctx.switchToHttp().getRequest()
13 |   return req.get('x-browser-Id')
14 | })
15 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/decorators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client'
2 | export * from './device-id'
3 | export * from './ip'
4 | export * from './lang'
5 | export * from './user-agent'
6 | export * from './raw-body'
7 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/decorators/raw-body.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import * as rawBody from 'raw-body'
 3 | 
 4 | import { helper } from '@heyform-inc/utils'
 5 | 
 6 | export const HttpRawBody = createParamDecorator(
 7 |   async (_: any, ctx: ExecutionContext): Promise<string> => {
 8 |     const req = ctx.switchToHttp().getRequest()
 9 |     let payload: string
10 | 
11 |     if (req.readable) {
12 |       const raw = await rawBody(req)
13 |       payload = raw.toString('utf8').trim()
14 |     } else if (helper.isPlainObject(req.body) || helper.isArray(req.body)) {
15 |       payload = JSON.stringify(req.body, null, 2)
16 |     } else {
17 |       payload = req.body.toString()
18 |     }
19 | 
20 |     return payload
21 |   }
22 | )
23 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/decorators/user-agent.ts:
--------------------------------------------------------------------------------
 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'
 2 | import { GqlExecutionContext } from '@nestjs/graphql'
 3 | import 'reflect-metadata'
 4 | 
 5 | import { UserAgent as UserAgentInterface, parseUserAgent } from '../user-agent'
 6 | 
 7 | export const GqlUserAgent = createParamDecorator(
 8 |   (_: any, context: ExecutionContext): UserAgentInterface => {
 9 |     const ctx = GqlExecutionContext.create(context)
10 |     const { req } = ctx.getContext()
11 |     return parseUserAgent(req.get('user-agent'))
12 |   }
13 | )
14 | 
15 | export const HttpUserAgent = createParamDecorator(
16 |   (_: any, ctx: ExecutionContext): UserAgentInterface => {
17 |     const req = ctx.switchToHttp().getRequest()
18 |     return parseUserAgent(req.get('user-agent'))
19 |   }
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/directives/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lower'
2 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/directives/lower.ts:
--------------------------------------------------------------------------------
 1 | import { SchemaDirectiveVisitor } from 'apollo-server'
 2 | import { GraphQLField, defaultFieldResolver } from 'graphql'
 3 | 
 4 | export class LowerCaseDirective extends SchemaDirectiveVisitor {
 5 |   visitFieldDefinition(field: GraphQLField<any, any>) {
 6 |     const { resolve = defaultFieldResolver } = field
 7 |     field.resolve = async function (...args) {
 8 |       const result = await resolve.apply(this, args)
 9 |       if (typeof result === 'string') {
10 |         return result.toLowerCase()
11 |       }
12 |       return result
13 |     }
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/disposable-email.ts:
--------------------------------------------------------------------------------
1 | import * as blocklist from 'disposable-email-blocklist'
2 | 
3 | export function isDisposableEmail(email: string): boolean {
4 |   const [_, domain] = email.toLowerCase().split('@')
5 |   return ((blocklist as unknown) as string[]).includes(domain)
6 | }
7 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/gravatar/index.ts:
--------------------------------------------------------------------------------
1 | import { md5 } from '../crypto'
2 | 
3 | export function gravatar(email: string, size = 120): string {
4 |   return `https://secure.gravatar.com/avatar/${md5(email)}?s=${size}&d=mm&r=g`
5 | }
6 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/handlebars.ts:
--------------------------------------------------------------------------------
 1 | // @ts-ignore
 2 | import * as hbs from 'hbs'
 3 | 
 4 | const h = hbs as any
 5 | 
 6 | h.registerHelper('json', v1 => JSON.stringify(v1))
 7 | h.registerHelper('eq', (v1, v2) => v1 === v2)
 8 | h.registerHelper('ne', (v1, v2) => v1 !== v2)
 9 | 
10 | export default h
11 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './logger'
 2 | export * from './mongo'
 3 | export * from './anti-bot'
 4 | export * from './crypto'
 5 | export * from './decorators'
 6 | export * from './directives'
 7 | export * from './gravatar'
 8 | export * from './lower-case-scalar'
 9 | export * from './social-login'
10 | export * from './unsplash'
11 | export * from './smtp'
12 | export * from './user-agent'
13 | export * from './map-to-object'
14 | export * from './read-dir-sync'
15 | export * from './promise-timeout'
16 | export { default as hbs } from './handlebars'
17 | export * from './request-parser'
18 | export * from './random-number'
19 | export * from './disposable-email'
20 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/logger/index.ts:
--------------------------------------------------------------------------------
 1 | import { Logger as L } from '@nestjs/common'
 2 | 
 3 | export class Logger extends L {
 4 |   static info(message: any, context?: string): void {
 5 |     L.log(message, context)
 6 |   }
 7 | 
 8 |   static trace(message: any, trace?: string, context?: string): void {
 9 |     L.error(message, trace, context)
10 |   }
11 | 
12 |   static fatal(message: any, trace?: string, context?: string): void {
13 |     L.error(message, trace, context)
14 |   }
15 | 
16 |   info(message: any, context?: string): void {
17 |     this.log(message, context)
18 |   }
19 | 
20 |   trace(message: any, trace?: string, context?: string): void {
21 |     this.error(message, trace, context)
22 |   }
23 | 
24 |   fatal(message: any, trace?: string, context?: string): void {
25 |     this.error(message, trace, context)
26 |   }
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/lower-case-scalar.ts:
--------------------------------------------------------------------------------
 1 | import { CustomScalar, Scalar } from '@nestjs/graphql'
 2 | import { Kind, ValueNode } from 'graphql'
 3 | 
 4 | export class LowerCase extends String {}
 5 | 
 6 | @Scalar('LowerCase', () => LowerCase)
 7 | export class LowerCaseScalar implements CustomScalar<string, LowerCase> {
 8 |   description = 'Lower string custom scalar type'
 9 | 
10 |   parseValue(value: string): LowerCase {
11 |     return value.toLowerCase()
12 |   }
13 | 
14 |   serialize(value: LowerCase): string {
15 |     return value.toString()
16 |   }
17 | 
18 |   parseLiteral(ast: ValueNode): LowerCase | null {
19 |     if (ast.kind === Kind.STRING) {
20 |       return ast.value.toLowerCase()
21 |     }
22 |     return null
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/map-to-object.ts:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | 
 3 | export function mapToObject<T = any>(mapLike: any): T {
 4 |   if (helper.isEmpty(mapLike)) {
 5 |     return {} as T
 6 |   }
 7 | 
 8 |   // @ts-ignore
 9 |   return helper.isMap(mapLike) ? Object.fromEntries(mapLike) : mapLike
10 | }
11 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/mongo.ts:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | 
 3 | export function getUpdateQuery(
 4 |   obj: Record<string, any>,
 5 |   prefix: string,
 6 |   deep = true
 7 | ): Record<string, any> {
 8 |   let query: Record<string, any> = {}
 9 | 
10 |   Object.keys(obj).forEach(key => {
11 |     const value = obj[key]
12 | 
13 |     if (deep && helper.isPlainObject(value)) {
14 |       query = {
15 |         ...query,
16 |         ...getUpdateQuery(value, `${prefix}.${key}`)
17 |       }
18 |     } else if (!helper.isNil(value)) {
19 |       query[`${prefix}.${key}`] = value
20 |     }
21 |   })
22 | 
23 |   return query
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/random-number.ts:
--------------------------------------------------------------------------------
1 | export function randomNumber(min: number, max: number): number {
2 |   return Math.floor(Math.random() * (max - min + 1)) + min
3 | }
4 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/read-dir-sync.ts:
--------------------------------------------------------------------------------
1 | import { readdirSync } from 'fs'
2 | import { resolve } from 'path'
3 | 
4 | export function readDirSync(dir: string, ext: string) {
5 |   return readdirSync(dir)
6 |     .filter(filePath => filePath.endsWith(ext))
7 |     .map(filePath => resolve(dir, filePath))
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/request-parser.ts:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | 
 3 | export function requestParser(req: any, keys: string[]): any {
 4 |   const sources = ['body', 'query', 'params']
 5 |   let value: any
 6 | 
 7 |   for (const source of sources) {
 8 |     for (const key of keys) {
 9 |       const searchValue = req[source][key]
10 | 
11 |       if (helper.isValid(searchValue)) {
12 |         value = searchValue
13 |         break
14 |       }
15 |     }
16 |   }
17 | 
18 |   return value
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/server/src/utils/social-login/utils.ts:
--------------------------------------------------------------------------------
 1 | import axios from 'axios'
 2 | 
 3 | import { qs } from '@heyform-inc/utils'
 4 | 
 5 | export const request = axios.create({
 6 |   withCredentials: true,
 7 |   timeout: 6e4
 8 | }).request
 9 | 
10 | export function generateUrl(prefixUri: string, query: Record<string, any>): string {
11 |   const queryString = qs.stringify(query)
12 |   return `${prefixUri}?${queryString}`
13 | }
14 | 
15 | export const defaultLocales = ['en', 'zh-cn']
16 | 
17 | export function formatLocale(lang?: string, whiteList?: string[]): string {
18 |   const customWhiteList = whiteList || defaultLocales
19 |   const defaultLocale = customWhiteList[0]
20 | 
21 |   if (!lang) {
22 |     return defaultLocale
23 |   }
24 | 
25 |   const lower = lang.toLowerCase()
26 |   return customWhiteList.includes(lower) ? lower : defaultLocale
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/server/tsconfig.build.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "extends": "./tsconfig.json",
 3 | 	"exclude": [
 4 | 		"view",
 5 | 		"node_modules",
 6 | 		"mongoose",
 7 | 		"dist"
 8 | 	]
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/server/view/connect-stripe.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html>
 3 | <head>
 4 | 	<meta charset="utf-8"/>
 5 | 	<meta content="width=device-width, initial-scale=1" name="viewport" />
 6 | 	<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
 7 | 	<meta name="robots" content="noindex">
 8 | 	<title>Connect to Stripe - HeyForm</title>
 9 | </head>
10 | <body>
11 | <script>
12 | 	var origin = window.location.origin;
13 | 
14 | 	if (window.opener && window.opener.origin === origin) {
15 | 		var data = {
16 | 			source: 'heyform-connect-stripe',
17 | 			payload: {{{json rendererData}}}
18 | 		};
19 | 		window.opener.postMessage(data, origin);
20 | 	}
21 | </script>
22 | </body>
23 | </html>
24 | 


--------------------------------------------------------------------------------
/packages/server/view/social-login.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html>
 3 | <head>
 4 |   <meta charset="utf-8"/>
 5 |   <meta name="robots" content="noindex">
 6 |   <meta http-equiv="refresh" content="0; url='{{redirectUri}}'">
 7 | </head>
 8 | <body>
 9 |   Redirecting to <a href="{{redirectUri}}">HeyForm</a>
10 | </body>
11 | </html>
12 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 80,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid"
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/audience.ts:
--------------------------------------------------------------------------------
 1 | export interface GroupModel {
 2 |   id: string
 3 |   teamId?: string
 4 |   name: string
 5 |   contactCount?: number
 6 | }
 7 | 
 8 | export interface ContactModel {
 9 |   id: string
10 |   teamId: string
11 |   fullName: string
12 |   email: string
13 |   jobTitle?: string
14 |   avatar?: string
15 |   phoneNumber?: string
16 |   note?: string
17 |   groups?: GroupModel[]
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/enums/keyboard.ts:
--------------------------------------------------------------------------------
 1 | export enum KeyCodeEnum {
 2 |   BACKSPACE = 8,
 3 |   TAB = 9,
 4 |   ENTER = 13,
 5 |   ESC = 27,
 6 |   SPACE = 32,
 7 |   LEFT = 37,
 8 |   UP = 38,
 9 |   RIGHT = 39,
10 |   DOWN = 40,
11 |   DELETE = 46,
12 |   VOID = 229
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/enums/social-login.ts:
--------------------------------------------------------------------------------
1 | export enum SocialLoginTypeEnum {
2 |   APPLE = 'apple',
3 |   GOOGLE = 'google',
4 |   WECHAT = 'wechat',
5 |   GOOGLE_ONE_TAP = 'google-one-tap'
6 | }
7 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/enums/submission.ts:
--------------------------------------------------------------------------------
 1 | export enum SubmissionCategoryEnum {
 2 |   INBOX = 'inbox',
 3 |   SPAM = 'spam',
 4 |   STARRED = 'starred',
 5 |   ARCHIVE = 'archive'
 6 | }
 7 | 
 8 | export enum SubmissionStatusEnum {
 9 |   PUBLIC = 1,
10 |   PRIVATE = 2,
11 |   DELETED = 3
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './enums/form'
 2 | export * from './enums/keyboard'
 3 | export * from './enums/submission'
 4 | export * from './enums/social-login'
 5 | export * from './constants/form'
 6 | export * from './audience'
 7 | export * from './form'
 8 | export * from './submission'
 9 | export * from './unsplash'
10 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/src/unsplash.ts:
--------------------------------------------------------------------------------
1 | export interface UnsplashImage {
2 |   id: string
3 |   url: string
4 |   thumbUrl: string
5 |   downloadUrl: string
6 |   author: string
7 |   authorUrl: string
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2017",
 4 |     "forceConsistentCasingInFileNames": true,
 5 |     "esModuleInterop": true,
 6 |     "noUnusedLocals": true,
 7 |     "noUnusedParameters": true,
 8 |     "strict": true,
 9 |     "removeComments": true,
10 |     "allowSyntheticDefaultImports": true,
11 |     "listEmittedFiles": true,
12 |     "listFiles": true,
13 |     "allowJs": false,
14 |     "declaration": true,
15 |     "sourceMap": true,
16 |     "importHelpers": true,
17 |     "resolveJsonModule": true,
18 | 		"moduleResolution": "node",
19 |     "skipLibCheck": true
20 |   },
21 |   "exclude": [
22 |     "coverage",
23 |     "lib",
24 |     "node_modules"
25 |   ]
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/shared-types-enums/tsup.config.js:
--------------------------------------------------------------------------------
 1 | /** @type {import('tsup').Options} */
 2 | module.exports = {
 3 | 	target: 'esnext',
 4 | 	dts: true,
 5 | 	sourcemap: true,
 6 | 	entry: ['src/index.ts'],
 7 | 	format: ['cjs', 'esm'],
 8 | 	splitting: false,
 9 | 	treeshake: true,
10 | 	clean: true
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/utils/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 80,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid"
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/utils/src/clone.ts:
--------------------------------------------------------------------------------
1 | import _clone from 'clone'
2 | 
3 | export function clone<T>(obj: T): T {
4 |   return _clone(obj)
5 | }
6 | 


--------------------------------------------------------------------------------
/packages/utils/src/index.ts:
--------------------------------------------------------------------------------
 1 | export * from 'deep-object-diff'
 2 | export { default as qs } from 'qs'
 3 | export * from './bytes'
 4 | export * from './clone'
 5 | export * from './color'
 6 | export * from './conv'
 7 | export * from './date'
 8 | export { default as helper } from './helper'
 9 | export * from './second'
10 | export * from './mime'
11 | export * from './nanoid'
12 | export * from './object'
13 | export * from './random'
14 | export * from './slugify'
15 | export * from './type'
16 | export * from './uuid'
17 | 


--------------------------------------------------------------------------------
/packages/utils/src/nanoid.ts:
--------------------------------------------------------------------------------
 1 | import { customAlphabet } from 'nanoid'
 2 | 
 3 | const NANOID_ALPHABET =
 4 |   'ModuleSymbhasOwnPr0123456789ABCDEFGHNRVfgctiUvzKqYTJkLxpZXIjQW'
 5 | 
 6 | export function nanoid(len = 21): string {
 7 |   return nanoidCustomAlphabet(NANOID_ALPHABET, len)
 8 | }
 9 | 
10 | export function nanoidCustomAlphabet(alphabet: string, len = 21): string {
11 |   const generate = customAlphabet(alphabet, len)
12 |   return generate()
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/utils/src/second.ts:
--------------------------------------------------------------------------------
 1 | import _ms from 'ms'
 2 | 
 3 | export function hs(arg: string): number | undefined {
 4 |   const value = ms(arg)
 5 | 
 6 |   if (value) {
 7 |     return Math.round(value / 1_000)
 8 |   }
 9 | }
10 | 
11 | export function ms(arg: string): number | undefined {
12 |   return _ms(arg)
13 | }
14 | 
15 | export const toSecond = hs
16 | export const toMillisecond = ms
17 | 


--------------------------------------------------------------------------------
/packages/utils/src/slugify.ts:
--------------------------------------------------------------------------------
 1 | import slug from 'slugify'
 2 | 
 3 | export interface SlugifyOptions {
 4 |   replacement?: string
 5 |   remove?: RegExp
 6 |   lower?: boolean
 7 |   strict?: boolean
 8 |   locale?: string
 9 |   trim?: boolean
10 | }
11 | 
12 | export function slugify(text: string, options?: SlugifyOptions): string {
13 |   return slug(text, {
14 |     replacement: '_',
15 |     lower: true,
16 |     ...options
17 |   })
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/utils/src/type.ts:
--------------------------------------------------------------------------------
 1 | const toString = Object.prototype.toString
 2 | 
 3 | export function type(obj: any): string {
 4 |   if (obj === null) {
 5 |     return 'null'
 6 |   }
 7 | 
 8 |   let type: string = typeof obj
 9 | 
10 |   if (type !== 'object') {
11 |     return type
12 |   }
13 | 
14 |   type = toString.call(obj).slice(8, -1)
15 | 
16 |   const typeLower = type.toLowerCase()
17 | 
18 |   if (typeLower !== 'object') {
19 |     if (
20 |       typeLower === 'number' ||
21 |       typeLower === 'boolean' ||
22 |       typeLower === 'string'
23 |     ) {
24 |       return type
25 |     }
26 |     return typeLower
27 |   }
28 | 
29 |   return typeLower
30 | }
31 | 


--------------------------------------------------------------------------------
/packages/utils/src/uuid.ts:
--------------------------------------------------------------------------------
1 | export { v4 as uuidv4, v5 as uuidv5 } from 'uuid'
2 | 


--------------------------------------------------------------------------------
/packages/utils/test/clone.test.ts:
--------------------------------------------------------------------------------
 1 | import { test, expect } from 'vitest'
 2 | import { clone } from '../src'
 3 | 
 4 | const obj = {
 5 |   a: {
 6 |     w: 'hello'
 7 |   },
 8 |   b: [
 9 |     {
10 |       x: 2,
11 |       y: true
12 |     }
13 |   ]
14 | }
15 | 
16 | test('clone nested object', () => {
17 |   expect(clone(obj)).toStrictEqual(obj)
18 | })
19 | 
20 | test('clone nested object', () => {
21 |   const copyObj = clone(obj)
22 |   copyObj.a.w = 'world'
23 | 
24 |   expect(obj.a.w).toBe('hello')
25 |   expect(copyObj.a.w).toBe('world')
26 |   expect(copyObj.b).toStrictEqual([
27 |     {
28 |       x: 2,
29 |       y: true
30 |     }
31 |   ])
32 | })
33 | 


--------------------------------------------------------------------------------
/packages/utils/test/nanoid.test.ts:
--------------------------------------------------------------------------------
 1 | import { test, expect } from 'vitest'
 2 | import { nanoid, nanoidCustomAlphabet } from '../src'
 3 | 
 4 | test('nanoid length', () => {
 5 |   expect(nanoid().length).toBe(21)
 6 | })
 7 | 
 8 | test('nanoid custom alphabet', () => {
 9 |   expect(nanoidCustomAlphabet('a', 6)).toBe('aaaaaa')
10 |   expect(nanoidCustomAlphabet('b')).toBe('bbbbbbbbbbbbbbbbbbbbb')
11 | })
12 | 


--------------------------------------------------------------------------------
/packages/utils/test/qs.test.ts:
--------------------------------------------------------------------------------
 1 | import { test, expect } from 'vitest'
 2 | import { qs } from '../src'
 3 | 
 4 | const obj = {
 5 |   a: [1, 2, 3],
 6 |   b: 'hello',
 7 |   c: true,
 8 |   d: undefined
 9 | }
10 | 
11 | const str = 'a%5B0%5D=1&a%5B1%5D=2&a%5B2%5D=3&b=hello&c=true'
12 | 
13 | test('stringify object', () => {
14 |   expect(qs.stringify(obj)).toBe(str)
15 | })
16 | 
17 | test('parse string', () => {
18 |   expect(qs.parse(str)).toStrictEqual({
19 |     a: ['1', '2', '3'],
20 |     b: 'hello',
21 |     c: 'true'
22 |   })
23 | })
24 | 
25 | test('parse array', () => {
26 |   expect(qs.parse([] as any)).toStrictEqual({})
27 | })
28 | 
29 | test('parse empty string', () => {
30 |   expect(qs.parse('')).toStrictEqual({})
31 | })
32 | 


--------------------------------------------------------------------------------
/packages/utils/test/slugify.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from 'vitest'
2 | import { slugify } from '../src'
3 | 
4 | test('slugify', () => {
5 |   expect(slugify('/user/sign/in')).toBe('usersignin')
6 |   expect(slugify('Hello World')).toBe('hello_world')
7 |   expect(slugify('Hello World', { replacement: '-' })).toBe('hello-world')
8 | })
9 | 


--------------------------------------------------------------------------------
/packages/utils/test/uuid.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from 'vitest'
2 | import { uuidv4, helper } from '../src'
3 | 
4 | test('uuidv4', () => {
5 |   expect(helper.isUUID(uuidv4())).toBe(true)
6 | })
7 | 


--------------------------------------------------------------------------------
/packages/utils/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2017",
 4 |     "forceConsistentCasingInFileNames": true,
 5 |     "esModuleInterop": true,
 6 |     "noUnusedLocals": true,
 7 |     "noUnusedParameters": true,
 8 |     "strict": true,
 9 |     "removeComments": true,
10 |     "allowSyntheticDefaultImports": true,
11 |     "listEmittedFiles": true,
12 |     "listFiles": true,
13 |     "allowJs": false,
14 |     "declaration": true,
15 |     "sourceMap": true,
16 |     "importHelpers": true,
17 |     "resolveJsonModule": true,
18 | 		"moduleResolution": "node",
19 |     "skipLibCheck": true
20 |   },
21 |   "exclude": [
22 |     "coverage",
23 |     "lib",
24 |     "node_modules"
25 |   ]
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/utils/tsup.config.js:
--------------------------------------------------------------------------------
 1 | /** @type {import('tsup').Options} */
 2 | module.exports = {
 3 | 	target: 'esnext',
 4 | 	dts: true,
 5 | 	sourcemap: true,
 6 | 	entry: ['src/index.ts'],
 7 | 	format: ['cjs', 'esm'],
 8 | 	splitting: false,
 9 | 	treeshake: true,
10 | 	clean: true
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/webapp/.env.example:
--------------------------------------------------------------------------------
 1 | VITE_HOMEPAGE_URL=http://127.0.0.1:3000
 2 | VITE_COOKIE_DOMAIN=127.0.0.1
 3 | 
 4 | VITE_STRIPE_PUBLISHABLE_KEY=
 5 | VITE_GEETEST_CAPTCHA_ID=
 6 | VITE_GOOGLE_RECAPTCHA_KEY=
 7 | 
 8 | VITE_DISABLE_LOGIN_WITH_GOOGLE=false
 9 | VITE_DISABLE_LOGIN_WITH_APPLE=false
10 | 
11 | VITE_VERIFY_USER_EMAIL=true
12 | VITE_APP_DISABLE_REGISTRATION=false
13 | 


--------------------------------------------------------------------------------
/packages/webapp/.prettierrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "printWidth": 100,
 3 |   "singleQuote": true,
 4 |   "useTabs": false,
 5 |   "semi": false,
 6 |   "tabWidth": 2,
 7 |   "trailingComma": "none",
 8 |   "bracketSpacing": true,
 9 |   "jsxBracketSameLine": false,
10 |   "arrowParens": "avoid",
11 |   "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
12 |   "importOrder": [
13 |     "^@/",
14 |     "^[../]",
15 |     "^[./]"
16 |   ],
17 |   "importOrderSeparation": true,
18 |   "importOrderSortSpecifiers": true,
19 |   "importOrderGroupNamespaceSpecifiers": false
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   plugins: {
3 | 		tailwindcss: require("./tailwind.config.js"),
4 |     autoprefixer: {},
5 |   },
6 | }
7 | 


--------------------------------------------------------------------------------
/packages/webapp/public/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/apple-touch-icon.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/email.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/facebookpixel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/facebookpixel.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/favicon.ico


--------------------------------------------------------------------------------
/packages/webapp/public/static/googleanalytics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/googleanalytics.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_120.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_128.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_180.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_192.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_512.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_maskable_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_maskable_128.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_maskable_192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_maskable_192.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/icon_maskable_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/icon_maskable_512.png


--------------------------------------------------------------------------------
/packages/webapp/public/static/webhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/heyform/heyform/c8856912b787038c3b0f21c56640f081fe636e32/packages/webapp/public/static/webhook.png


--------------------------------------------------------------------------------
/packages/webapp/src/components/RedirectUriLink.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { Link } from 'react-router-dom'
 3 | 
 4 | import { useQueryURL } from '@/utils'
 5 | 
 6 | interface RedirectUriLinkProps extends IComponentProps {
 7 |   href: string
 8 | }
 9 | 
10 | export const RedirectUriLink: FC<RedirectUriLinkProps> = ({ href, children, ...restProps }) => {
11 |   const to = useQueryURL(href)
12 | 
13 |   return (
14 |     <Link to={to} {...restProps}>
15 |       {children}
16 |     </Link>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/SubHeading.tsx:
--------------------------------------------------------------------------------
 1 | import { FC, ReactNode } from 'react'
 2 | 
 3 | interface SubHeadingProps extends IComponentProps {
 4 |   id?: string
 5 |   description?: ReactNode
 6 |   action?: ReactNode
 7 | }
 8 | 
 9 | export const SubHeading: FC<SubHeadingProps> = ({
10 |   children,
11 |   description,
12 |   action,
13 |   ...restProps
14 | }) => {
15 |   return (
16 |     <div className="mb-5 mt-11 flex items-center justify-between" {...restProps}>
17 |       <div>
18 |         <div className="subheading-title mb-3 text-base font-medium leading-relaxed text-gray-700">
19 |           {children}
20 |         </div>
21 |         {description && (
22 |           <div className="subheading-description mt-1 text-gray-500">{description}</div>
23 |         )}
24 |       </div>
25 |       {action && action}
26 |     </div>
27 |   )
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/BoldIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const BoldIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M6 12V3H11.85C14.3275 3 16.336 5.01472 16.336 7.5C16.336 9.98526 14.3275 12 11.85 12H6ZM6 12H13.664C16.1416 12 18.15 14.0147 18.15 16.5C18.15 18.9853 16.1416 21 13.664 21H6V12Z"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/CollapseIcon.tsx:
--------------------------------------------------------------------------------
1 | import type { FC } from 'react'
2 | 
3 | export const CollapseIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
4 |   <svg width="8" height="6" viewBox="0 0 8 6" xmlns="http://www.w3.org/2000/svg" {...props}>
5 |     <path d="M4 6l4-6H0z" fillRule="evenodd" fill="currentColor"></path>
6 |   </svg>
7 | )
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/ConcentricCirclesIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const ConcentricCirclesIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |     />
17 |     <path
18 |       d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
19 |       stroke="currentColor"
20 |       strokeWidth="2"
21 |       strokeLinecap="round"
22 |       strokeLinejoin="round"
23 |     />
24 |   </svg>
25 | )
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/DateRangeIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const DateRangeIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...props}>
 6 |       <g fill="none" fillRule="evenodd">
 7 |         <path
 8 |           stroke="currentColor"
 9 |           strokeLinecap="round"
10 |           strokeLinejoin="round"
11 |           strokeWidth="2"
12 |           d="M5,4 L5,0 M13,4 L13,0 M4,8 L14,8 M4,13 L10,13 M2,18 L16,18 C17.1046,18 18,17.1046 18,16 L18,4 C18,2.89543 17.1046,2 16,2 L2,2 C0.89543,2 0,2.89543 0,4 L0,16 C0,17.1046 0.89543,18 2,18 Z"
13 |           transform="translate(3 3)"
14 |         />
15 |         <rect width="24" height="24" />
16 |       </g>
17 |     </svg>
18 |   )
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/FullpageIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const FullpageIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg viewBox="0 0 219 200" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
 5 |     <rect x="0.47699" y="12.9384" width="218.238" height="187.062" fill="#BFDBFE" />
 6 |     <path
 7 |       d="M4.47699 0.5H214.715C216.648 0.5 218.215 2.06701 218.215 4V12.4384H0.97699V4C0.97699 2.06701 2.54399 0.5 4.47699 0.5Z"
 8 |       fill="#F4F4F4"
 9 |       stroke="#F2F2F2"
10 |     />
11 |     <rect x="6.08881" y="4.20886" width="4.36477" height="4.36477" rx="2.18238" fill="#D8D8D8" />
12 |     <rect x="15.1302" y="4.20886" width="4.36477" height="4.36477" rx="2.18238" fill="#D8D8D8" />
13 |     <rect x="24.1714" y="4.20886" width="4.36477" height="4.36477" rx="2.18238" fill="#D8D8D8" />
14 |   </svg>
15 | )
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/ItalicIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const ItalicIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M10 3H18M6 21H14M14.5 2.9762L9.5 21"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutCoverIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutCoverIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path
14 |         fillRule="evenodd"
15 |         clipRule="evenodd"
16 |         d="M3 9C3 5.68629 5.68629 3 9 3H55C58.3137 3 61 5.68629 61 9V31C61 34.3137 58.3137 37 55 37H9C5.68629 37 3 34.3137 3 31V9ZM24 22H36V24H24V22ZM40 16H24V18H40V16Z"
17 |         fill="currentColor"
18 |       />
19 |     </svg>
20 |   )
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutFloatLeftIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutFloatLeftIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path d="M36 22H48V24H36V22Z" fill="currentColor" />
14 |       <path d="M12 14H28V26H12V14Z" fill="currentColor" />
15 |       <path d="M36 16H52V18H36V16Z" fill="currentColor" />
16 |     </svg>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutFloatRightIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutFloatRightIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path d="M12 22H24V24H12V22Z" fill="currentColor" />
14 |       <path d="M36 14H52V26H36V14Z" fill="currentColor" />
15 |       <path d="M12 16H28V18H12V16Z" fill="currentColor" />
16 |     </svg>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutInlineIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutInlineIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path d="M20 9H32V11H20V9Z" fill="currentColor" />
14 |       <path d="M20 14H44V26H20V14Z" fill="currentColor" />
15 |       <path d="M20 29H36V31H20V29Z" fill="currentColor" />
16 |     </svg>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutSplitLeftIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutSplitLeftIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path d="M38 22H50V24H38V22Z" fill="currentColor" />
14 |       <path
15 |         d="M3 9C3 5.68629 5.68629 3 9 3H30V37H9C5.68629 37 3 34.3137 3 31V9Z"
16 |         fill="currentColor"
17 |       />
18 |       <path d="M38 16H54V18H38V16Z" fill="currentColor" />
19 |     </svg>
20 |   )
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LayoutSplitRightIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LayoutSplitRightIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="64"
 7 |       height="40"
 8 |       viewBox="0 0 64 40"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       {...props}
12 |     >
13 |       <path d="M9 22H21V24H9V22Z" fill="currentColor" />
14 |       <path
15 |         d="M34 3H55C58.3137 3 61 5.68629 61 9V31C61 34.3137 58.3137 37 55 37H34V3Z"
16 |         fill="currentColor"
17 |       />
18 |       <path d="M9 16H25V18H9V16Z" fill="currentColor" />
19 |     </svg>
20 |   )
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LikeIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LikeIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="24"
 7 |       height="24"
 8 |       viewBox="0 0 24 24"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       className="heyform-icon"
12 |       {...props}
13 |     >
14 |       <path
15 |         d="M7.5 4C4.46244 4 2 6.46245 2 9.5C2 15 8.5 20 12 21.1631C15.5 20 22 15 22 9.5C22 6.46245 19.5375 4 16.5 4C14.6399 4 12.9954 4.92345 12 6.3369C11.0046 4.92345 9.36015 4 7.5 4Z"
16 |         className="heyform-icon-fill heyform-icon-stroke"
17 |         strokeWidth="1"
18 |         strokeLinecap="round"
19 |         strokeLinejoin="round"
20 |       />
21 |     </svg>
22 |   )
23 | }
24 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/LongTextIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const LongTextIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M4 6H20M4 12H20M4 18H11"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/ShortTextIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const ShortTextIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M4 8H20M4 16H11"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/SignatureIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const SignatureIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M8.4 12H5.25C4.00736 12 3 13.0074 3 14.25C3 15.4926 4.00736 16.5 5.25 16.5H18.75C19.9926 16.5 21 17.5074 21 18.75C21 19.9926 19.9926 21 18.75 21H9.3M12 12V9.75L18.75 3L21 5.25L14.25 12H12Z"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/StarIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const StarIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 4 |   return (
 5 |     <svg
 6 |       width="24"
 7 |       height="24"
 8 |       viewBox="0 0 24 24"
 9 |       fill="none"
10 |       xmlns="http://www.w3.org/2000/svg"
11 |       className="heyform-icon"
12 |       {...props}
13 |     >
14 |       <path
15 |         d="M11.9993 2.5L8.9428 8.7388L2 9.74555L7.02945 14.6625L5.8272 21.5L11.9993 18.2096L18.1727 21.5L16.9793 14.6625L22 9.74555L15.0956 8.7388L11.9993 2.5Z"
16 |         className="heyform-icon-fill heyform-icon-stroke"
17 |         strokeWidth="1"
18 |         strokeLinejoin="round"
19 |       />
20 |     </svg>
21 |   )
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/ThankYouIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const ThankYouIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M11 5L11 19M11 5C11 3.89543 11.8954 3 13 3H19C20.1046 3 21 3.89543 21 5V19C21 20.1046 20.1046 21 19 21H13C11.8954 21 11 20.1046 11 19M11 5H9C7.89543 5 7 5.89543 7 7M11 19H9C7.89543 19 7 18.1046 7 17M7 17H5C3.89543 17 3 16.1046 3 15L3 9C3 7.89543 3.89543 7 5 7H7M7 17L7 7"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/WebsiteIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const WebsiteIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M14.7 10.6H10.2C8.21176 10.6 6.6 12.2118 6.6 14.2C6.6 16.1882 8.21176 17.8 10.2 17.8H17.4C19.3882 17.8 21 16.1882 21 14.2C21 13.287 20.6601 12.4534 20.1 11.8188M3.9 12.9812C3.33987 12.3466 3 11.513 3 10.6C3 8.61176 4.61177 7 6.6 7H13.8C15.7882 7 17.4 8.61176 17.4 10.6C17.4 12.5882 15.7882 14.2 13.8 14.2H9.3"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/WelcomeIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const WelcomeIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M13 19V5M13 19C13 20.1046 12.1046 21 11 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3L11 3C12.1046 3 13 3.89543 13 5M13 19H15C16.1046 19 17 18.1046 17 17M13 5H15C16.1046 5 17 5.89543 17 7M17 7H19C20.1046 7 21 7.89543 21 9V15C21 16.1046 20.1046 17 19 17H17M17 7V17"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/WorkspaceIcon.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | export const WorkspaceIcon: FC<IComponentProps<HTMLOrSVGElement>> = ({
 5 |   className,
 6 |   ...restProps
 7 | }) => (
 8 |   <svg
 9 |     className={clsx('avatar-placeholder', className)}
10 |     viewBox="0 0 28 28"
11 |     xmlns="http://www.w3.org/2000/svg"
12 |     {...restProps}
13 |   >
14 |     <path
15 |       d="M15 19.25a.75.75 0 001.086.67l3-1.5a.75.75 0 00.415-.67v-4.323a.75.75 0 00-1.086-.67l-3 1.5a.75.75 0 00-.415.67v4.323zm3.159-8.043a.75.75 0 000-1.341l-3.573-1.787a.75.75 0 00-.67 0l-3.574 1.787a.75.75 0 000 1.34l3.573 1.787a.749.749 0 00.67 0l3.574-1.786zm-8.074 1.55a.75.75 0 00-1.085.67v4.323a.75.75 0 00.415.67l3 1.5a.75.75 0 001.085-.67v-4.323a.75.75 0 00-.415-.67l-3-1.5z"
16 |       fill="currentColor"
17 |     />
18 |   </svg>
19 | )
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/icons/YesOrNoIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const YesOrNoIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 4 |   <svg
 5 |     width="24"
 6 |     height="24"
 7 |     viewBox="0 0 24 24"
 8 |     fill="none"
 9 |     xmlns="http://www.w3.org/2000/svg"
10 |     {...props}
11 |   >
12 |     <path
13 |       d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3M12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3M12 21V3"
14 |       stroke="currentColor"
15 |       strokeWidth="2"
16 |       strokeLinecap="round"
17 |       strokeLinejoin="round"
18 |     />
19 |   </svg>
20 | )
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './icons'
 2 | export * from './layouts'
 3 | export * from './numberRange'
 4 | export * from './photoPicker'
 5 | export * from './Async'
 6 | export * from './PhotoPickerField'
 7 | export * from './SwitchField'
 8 | export * from './Pagination'
 9 | export * from './DragUploader'
10 | export * from './RedirectUriLink'
11 | export * from './MobilePhoneCode'
12 | export * from './CopyButton'
13 | export * from './Uploader'
14 | export * from './Heading'
15 | export * from './SubHeading'
16 | export * from './TagGroup'
17 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/AuthLayout.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { AuthGuard, CommonLayout } from '@/components'
 4 | 
 5 | export const AuthLayout: FC<IComponentProps> = ({ children }) => {
 6 |   return (
 7 |     <AuthGuard>
 8 |       <CommonLayout>{children}</CommonLayout>
 9 |     </AuthGuard>
10 |   )
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/CommonLayout.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | export const CommonLayout: FC<IComponentProps> = ({ children }) => {
 4 |   return (
 5 |     <div className="flex min-h-screen flex-col justify-center bg-slate-50 py-12 sm:px-6 lg:px-8">
 6 |       <div className="sm:mx-auto sm:w-full sm:max-w-md">{children}</div>
 7 |     </div>
 8 |   )
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/FormLayout.tsx:
--------------------------------------------------------------------------------
 1 | import { FC } from 'react'
 2 | 
 3 | import { FormEmbedModal } from '@/pages/form/views/FormEmbedModal'
 4 | import { FormNavbar } from '@/pages/form/views/FormNavbar'
 5 | import { FormPreviewModal } from '@/pages/form/views/FormPreviewModal'
 6 | import { FormShareModal } from '@/pages/form/views/FormShareModal'
 7 | 
 8 | import { FormGuardLayout } from './FormGuardLayout'
 9 | 
10 | export const FormLayout: FC<IComponentProps> = ({ children }) => {
11 |   return (
12 |     <FormGuardLayout>
13 |       <div className="flex h-screen flex-col text-sm print:h-auto">
14 |         <FormNavbar />
15 |         <div className="content flex-1 bg-slate-50">{children}</div>
16 |       </div>
17 | 
18 |       <FormPreviewModal />
19 |       <FormShareModal />
20 |       <FormEmbedModal />
21 |     </FormGuardLayout>
22 |   )
23 | }
24 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/PublicLayout.tsx:
--------------------------------------------------------------------------------
1 | import type { FC } from 'react'
2 | 
3 | export const PublicLayout: FC<IComponentProps> = ({ children }) => {
4 |   return <>{children}</>
5 | }
6 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/index.scss:
--------------------------------------------------------------------------------
1 | .workspace-container {
2 |   @apply min-h-screen;
3 | 
4 |   @media (min-width: 768px) {
5 |     padding-left: 16rem;
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/layouts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CommonLayout'
2 | export * from './AuthGuard'
3 | export * from './AuthLayout'
4 | export * from './WorkspaceGuard'
5 | export * from './WorkspaceLayout'
6 | export * from './FormGuardLayout'
7 | export * from './FormLayout'
8 | export * from './PublicLayout'
9 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/numberRange/style.scss:
--------------------------------------------------------------------------------
 1 | .number-range {
 2 |   @apply flex items-center w-full;
 3 | 
 4 |   .select {
 5 |     @apply w-auto;
 6 |   }
 7 | 
 8 |   &.number-range-unlimited {
 9 |     .select {
10 |       @apply w-full;
11 |     }
12 |   }
13 | 
14 |   .select-button {
15 |     @apply pr-7 cursor-pointer;
16 |   }
17 | 
18 |   .input {
19 |     @apply px-2;
20 |   }
21 | }
22 | 
23 | .number-range-popup {
24 |   @apply w-56 #{!important};
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/photoPicker/style.scss:
--------------------------------------------------------------------------------
 1 | .photo-picker {
 2 |   .modal-body {
 3 |     @apply p-0 overflow-hidden;
 4 |   }
 5 | 
 6 |   .modal-close-button {
 7 |     @apply top-3 right-5;
 8 |   }
 9 | 
10 |   .tabs-wrapper {
11 |     @apply flex flex-col;
12 |   }
13 | 
14 |   .tabs-nav-list {
15 |     @apply px-4;
16 |   }
17 | 
18 |   .tabs-pane-group {
19 |     @apply h-96;
20 |     @extend .scrollbar;
21 |   }
22 | 
23 |   .tabs-pane {
24 |     @apply h-full p-4;
25 |   }
26 | 
27 |   .drag-uploader {
28 |     @apply h-full;
29 |   }
30 | }
31 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/avatar/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { AvatarProps } from './Avatar'
 4 | import Avatar from './Avatar'
 5 | import type { AvatarGroupProps } from './Group'
 6 | import Group from './Group'
 7 | 
 8 | type ExportAvatarType = FC<AvatarProps> & {
 9 |   Group: FC<AvatarGroupProps>
10 | }
11 | 
12 | const ExportAvatar = Avatar as ExportAvatarType
13 | ExportAvatar.Group = Group
14 | 
15 | export default ExportAvatar
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/avatar/style.scss:
--------------------------------------------------------------------------------
 1 | .avatar {
 2 |   @apply inline-flex items-center justify-center overflow-hidden w-8 h-8 bg-slate-200 bg-opacity-70;
 3 | 
 4 |   img {
 5 |     @apply h-full w-full object-cover;
 6 |   }
 7 | 
 8 |   .avatar-text {
 9 |     @apply text-lg font-medium leading-none text-slate-500;
10 |   }
11 | 
12 |   svg {
13 |     @apply text-opacity-70 text-slate-400;
14 |   }
15 | }
16 | 
17 | .avatar-rounded {
18 |   @apply rounded-md;
19 | }
20 | 
21 | .avatar-circular {
22 |   @apply rounded-full;
23 | }
24 | 
25 | .avatar-placeholder {
26 |   @apply h-full w-full text-slate-300;
27 | }
28 | 
29 | .avatar-group {
30 |   @apply flex -space-x-2 overflow-hidden;
31 | 
32 |   .avatar {
33 |     @apply ring-2 ring-white;
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/button/Group.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import { IComponentProps } from '../typing'
 5 | 
 6 | export type ButtonGroupProps = IComponentProps
 7 | 
 8 | const Group: FC<ButtonGroupProps> = ({ className, children, ...restProps }) => {
 9 |   return (
10 |     <span className={clsx('button-group', className)} {...restProps}>
11 |       {children}
12 |     </span>
13 |   )
14 | }
15 | 
16 | export default Group
17 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/button/Link.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import type { ButtonProps } from './Button'
 5 | import Button from './Button'
 6 | 
 7 | const Link: FC<ButtonProps> = ({ className, children, ...restProps }) => {
 8 |   return (
 9 |     <Button className={clsx('button-link', className)} {...restProps}>
10 |       {children}
11 |     </Button>
12 |   )
13 | }
14 | 
15 | export default Link
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/button/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { ButtonProps } from './Button'
 4 | import Button from './Button'
 5 | import type { ButtonGroupProps } from './Group'
 6 | import Group from './Group'
 7 | import Link from './Link'
 8 | 
 9 | type ExportButtonType = FC<ButtonProps> & {
10 |   Group: FC<ButtonGroupProps>
11 |   Link: FC<ButtonProps>
12 | }
13 | 
14 | const ExportButton = Button as ExportButtonType
15 | 
16 | ExportButton.Group = Group
17 | ExportButton.Link = Link
18 | 
19 | export default ExportButton
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/checkbox/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { CheckboxProps } from './Checkbox'
 4 | import Checkbox from './Checkbox'
 5 | import type { CheckboxGroupProps } from './Group'
 6 | import Group from './Group'
 7 | 
 8 | type ExportCheckboxType = FC<CheckboxProps> & {
 9 |   Group: FC<CheckboxGroupProps>
10 | }
11 | 
12 | const ExportCheckbox = Checkbox as ExportCheckboxType
13 | ExportCheckbox.Group = Group
14 | 
15 | export default ExportCheckbox
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/checkbox/style.scss:
--------------------------------------------------------------------------------
 1 | .checkbox-wrapper {
 2 |   @apply relative flex items-start;
 3 | }
 4 | 
 5 | .checkbox {
 6 |   @apply flex items-center h-5;
 7 | }
 8 | 
 9 | .checkbox-input {
10 |   @apply focus:ring-blue-600 h-4 w-4 text-blue-700 border-gray-300;
11 | 
12 |   &[type=checkbox] {
13 |     @apply rounded;
14 |   }
15 | }
16 | 
17 | .checkbox-input-checked {
18 | }
19 | 
20 | .checkbox-description {
21 |   @apply ml-3 sm:text-sm font-medium text-slate-700;
22 | }
23 | 
24 | .checkbox-group {
25 |   @apply space-y-4;
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/color-picker/helper.ts:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | 
 3 | export function rgbaToString(type: 'hex' | 'rgba', rgba: number[]) {
 4 |   if (helper.isValidArray(rgba)) {
 5 |     const [r, g, b, alpha] = rgba!
 6 | 
 7 |     if (type === 'hex') {
 8 |       let value = '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')
 9 | 
10 |       if (alpha < 1) {
11 |         value += Math.round(alpha * 0xff)
12 |           .toString(16)
13 |           .padStart(2, '0')
14 |       }
15 | 
16 |       return value
17 |     } else {
18 |       return `rgba(${r}, ${g}, ${b}, ${alpha})`
19 |     }
20 |   }
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/date-picker/store.ts:
--------------------------------------------------------------------------------
 1 | import { Dayjs } from 'dayjs'
 2 | import { createContext } from 'react'
 3 | 
 4 | import { DatePickerCommonOptions } from './common'
 5 | 
 6 | interface IStore extends DatePickerCommonOptions {
 7 |   format?: string
 8 |   timeFormat?: string
 9 |   value?: Dayjs
10 |   temp: Dayjs
11 |   current: Dayjs
12 |   isYearPickerOpen?: boolean
13 |   isMonthPickerOpen?: boolean
14 |   togglePicker: () => void
15 |   toggleYearPicker: () => void
16 |   toggleMonthPicker: () => void
17 |   toPrevious: () => void
18 |   toNext: () => void
19 |   updateYear: (year: number) => void
20 |   updateMonth: (month: number) => void
21 |   updateValue: (date: Dayjs) => void
22 |   setIsOpen: (open: boolean) => void
23 | }
24 | 
25 | export const DatePickerStore = createContext<IStore>({} as IStore)
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/dropdown/style.scss:
--------------------------------------------------------------------------------
 1 | .dropdown {
 2 |   @apply relative inline-block text-left;
 3 | 
 4 |   .menus {
 5 |     @apply absolute z-10 transition ease-in-out duration-100;
 6 |   }
 7 | 
 8 |   &.dropdown-top-left {
 9 |     .menus {
10 |       @apply origin-top-left left-0;
11 |     }
12 |   }
13 | 
14 |   &.dropdown-top-right {
15 |     .menus {
16 |       @apply origin-top-right right-0;
17 |     }
18 |   }
19 | }
20 | 
21 | .dropdown-trigger {
22 |   @apply w-full;
23 | }
24 | 
25 | .dropdown-popup-enter-active,
26 | .dropdown-popup-exit {
27 |   @apply transform opacity-100 scale-100;
28 | }
29 | 
30 | .dropdown-popup-enter,
31 | .dropdown-popup-exit-active {
32 |   @apply transform opacity-0 scale-95;
33 | }
34 | 
35 | .dropdown-popup-exit-done {
36 |   @apply hidden;
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/empty-states/style.scss:
--------------------------------------------------------------------------------
 1 | .empty-states {
 2 |   @apply text-center;
 3 | }
 4 | 
 5 | .empty-states-icon {
 6 |   svg {
 7 |     @apply mx-auto h-12 w-12 text-slate-400;
 8 |   }
 9 | }
10 | 
11 | .empty-states-title {
12 |   @apply mt-2 text-base font-medium text-slate-900;
13 | }
14 | 
15 | .empty-states-description {
16 |   @apply mt-1 w-full mx-auto text-sm text-slate-500;
17 | 
18 |   @media (min-width: 768px) {
19 |     width: 32rem;
20 |   }
21 | }
22 | 
23 | .empty-states-action {
24 |   @apply mt-6;
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/form/index.ts:
--------------------------------------------------------------------------------
 1 | import Form from 'rc-field-form'
 2 | import type { FC } from 'react'
 3 | 
 4 | import type { CustomFormProps } from './CustomForm'
 5 | import CustomForm from './CustomForm'
 6 | import type { FormItemProps } from './FormItem'
 7 | import FormItem from './FormItem'
 8 | import type { SwitchItemProps } from './SwitchItem'
 9 | import SwitchItem from './SwitchItem'
10 | 
11 | export { useForm } from 'rc-field-form'
12 | 
13 | type ExportFormType = typeof Form & {
14 |   Item: FC<FormItemProps>
15 |   Switch: FC<SwitchItemProps>
16 |   Custom: FC<CustomFormProps>
17 | }
18 | 
19 | const ExportForm = Form as ExportFormType
20 | 
21 | ExportForm.Item = FormItem
22 | ExportForm.Switch = SwitchItem
23 | ExportForm.Custom = CustomForm
24 | 
25 | export default ExportForm
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/heading/style.scss:
--------------------------------------------------------------------------------
 1 | .heading {
 2 |   @apply lg:flex lg:items-center lg:justify-between;
 3 | }
 4 | 
 5 | .heading-flex-auto {
 6 |   @apply flex-1 min-w-0;
 7 | }
 8 | 
 9 | .heading-left,
10 | .heading-icon {
11 |   @apply flex items-center;
12 | }
13 | 
14 | .heading-icon {
15 |   @apply mr-5;
16 | }
17 | 
18 | .heading-title {
19 |   @apply text-3xl font-bold text-slate-900;
20 | }
21 | 
22 | .heading-description {
23 |   @apply text-sm font-medium text-slate-500 mt-1 flex flex-col sm:flex-row sm:flex-wrap sm:space-x-6;
24 | }
25 | 
26 | .heading-actions {
27 |   @apply mt-6 flex flex-col-reverse justify-items-stretch space-y-4 space-y-reverse sm:flex-row-reverse sm:justify-end sm:space-x-reverse sm:space-y-0 sm:space-x-3 md:mt-0 md:flex-row md:space-x-3;
28 | }
29 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/icons/DefaultAvatarIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../typing'
 4 | 
 5 | const DefaultAvatarIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 6 |   return (
 7 |     <svg fill="currentColor" viewBox="0 0 24 24" {...props}>
 8 |       <path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
 9 |     </svg>
10 |   )
11 | }
12 | 
13 | export default DefaultAvatarIcon
14 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/icons/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DefaultAvatarIcon } from './DefaultAvatarIcon'
2 | export { default as EyeCloseIcon } from './EyeCloseIcon'
3 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/input/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { InputProps } from './Input'
 4 | import Input from './Input'
 5 | import type { InputPasswordProps } from './Password'
 6 | import Password from './Password'
 7 | import type { InputSearchProps } from './Search'
 8 | import Search from './Search'
 9 | import type { TextareaProps } from './Textarea'
10 | import Textarea from './Textarea'
11 | 
12 | type ExportInputType = FC<InputProps> & {
13 |   Password: FC<InputPasswordProps>
14 |   Textarea: FC<TextareaProps>
15 |   Search: FC<InputSearchProps>
16 | }
17 | 
18 | const ExportInput = Input as ExportInputType
19 | 
20 | ExportInput.Password = Password
21 | ExportInput.Textarea = Textarea
22 | ExportInput.Search = Search
23 | 
24 | export default ExportInput
25 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/loader/index.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC, SVGAttributes } from 'react'
 3 | 
 4 | const Loader: FC<SVGAttributes<HTMLOrSVGElement>> = ({ className, ...restProps }) => {
 5 |   return (
 6 |     <svg
 7 |       width="22"
 8 |       height="5"
 9 |       viewBox="0 0 21 5"
10 |       fill="none"
11 |       xmlns="http://www.w3.org/2000/svg"
12 |       className={clsx('loader', className)}
13 |       {...restProps}
14 |     >
15 |       <rect className="loader-span" width="5" height="5" rx="2.5" fill="currentColor" />
16 |       <rect className="loader-span" x="8" width="5" height="5" rx="2.5" fill="currentColor" />
17 |       <rect className="loader-span" x="16" width="5" height="5" rx="2.5" fill="currentColor" />
18 |     </svg>
19 |   )
20 | }
21 | 
22 | export default Loader
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/loader/style.scss:
--------------------------------------------------------------------------------
 1 | @keyframes loader-blink {
 2 |   0% {
 3 |     opacity: 0.2;
 4 |   }
 5 |   20% {
 6 |     opacity: 1;
 7 |   }
 8 |   100% {
 9 |     opacity: 0.2;
10 |   }
11 | }
12 | 
13 | .loader-span {
14 |   animation-name: loader-blink;
15 |   animation-duration: 1.4s;
16 |   animation-iteration-count: infinite;
17 |   animation-fill-mode: both;
18 | 
19 |   &:nth-of-type(2) {
20 |     animation-delay: 0.2s;
21 |   }
22 |   
23 |   &:nth-of-type(3) {
24 |     animation-delay: 0.4s;
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/menu/Divider.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import { IComponentProps } from '../typing'
 5 | 
 6 | const MenuDivider: FC<IComponentProps<HTMLDivElement>> = ({ className, ...restProps }) => {
 7 |   return <div className={clsx('menu-divider', className)} role="none" {...restProps} />
 8 | }
 9 | 
10 | export default MenuDivider
11 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/menu/Label.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | import { MouseEvent, ReactNode } from 'react'
 4 | 
 5 | import { IComponentProps } from '../typing'
 6 | 
 7 | export interface MenuLabelProps extends Omit<IComponentProps, 'onClick'> {
 8 |   icon?: ReactNode
 9 |   label: ReactNode
10 |   onClick?: (event?: MouseEvent<HTMLDivElement>) => void
11 | }
12 | 
13 | const MenuLabel: FC<MenuLabelProps> = ({ className, icon, label, onClick, ...restProps }) => {
14 |   return (
15 |     <div className={clsx('menu-label', className)} role="none" onClick={onClick} {...restProps}>
16 |       {icon}
17 |       {label}
18 |     </div>
19 |   )
20 | }
21 | 
22 | export default MenuLabel
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/menu/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '../typing'
 4 | import MenuDivider from './Divider'
 5 | import type { MenuItemProps } from './Item'
 6 | import MenuItem from './Item'
 7 | import type { MenuLabelProps } from './Label'
 8 | import MenuLabel from './Label'
 9 | import type { MenusProps } from './Menus'
10 | import Menus from './Menus'
11 | 
12 | type ExportMenusType = FC<MenusProps> & {
13 |   Item: FC<MenuItemProps>
14 |   Label: FC<MenuLabelProps>
15 |   Divider: FC<IComponentProps<HTMLDivElement>>
16 | }
17 | 
18 | const ExportMenus = Menus as unknown as ExportMenusType
19 | ExportMenus.Item = MenuItem
20 | ExportMenus.Label = MenuLabel
21 | ExportMenus.Divider = MenuDivider
22 | 
23 | export default ExportMenus
24 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/modal/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { ConfirmModalProps } from './Confirm'
 4 | import Confirm from './Confirm'
 5 | import type { ModalProps } from './Modal'
 6 | import Modal from './Modal'
 7 | 
 8 | type ExportModalType = FC<ModalProps> & {
 9 |   Confirm: FC<ConfirmModalProps>
10 | }
11 | 
12 | const ExportModal = Modal as ExportModalType
13 | ExportModal.Confirm = Confirm
14 | 
15 | export default ExportModal
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/navbar/index.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import { IComponentProps } from '../typing'
 5 | 
 6 | const Navbar: FC<IComponentProps> = ({ className, children, ...restProps }) => {
 7 |   return (
 8 |     <div className={clsx('navbar', className)} {...restProps}>
 9 |       <nav aria-label="Tabs">{children}</nav>
10 |     </div>
11 |   )
12 | }
13 | 
14 | export default Navbar
15 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/navbar/style.scss:
--------------------------------------------------------------------------------
 1 | .navbar {
 2 |   @apply border-b border-gray-200;
 3 | 
 4 |   nav {
 5 |     @apply mt-2 -mb-px flex space-x-8;
 6 |   }
 7 | 
 8 |   a {
 9 |     @apply border-transparent font-medium text-slate-500 hover:text-slate-700 hover:border-gray-200 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm;
10 | 
11 |     &.active {
12 |       @apply border-blue-600 text-blue-700;
13 |     }
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/portal/index.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC, ReactNode } from 'react'
 2 | import { createPortal } from 'react-dom'
 3 | 
 4 | export interface PortalProps {
 5 |   visible?: boolean
 6 |   container?: HTMLElement
 7 |   children: ReactNode
 8 | }
 9 | 
10 | const Portal: FC<PortalProps> = ({ visible, container, children }) => {
11 |   if (!visible || !children) {
12 |     return null
13 |   }
14 | 
15 |   return createPortal(children, container || document.body)
16 | }
17 | 
18 | export default Portal
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/radio/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { RadioGroupProps } from './Group'
 4 | import Group from './Group'
 5 | import type { RadioProps } from './Radio'
 6 | import Radio from './Radio'
 7 | 
 8 | type ExportRadioType = FC<RadioProps> & {
 9 |   Group: FC<RadioGroupProps>
10 | }
11 | 
12 | const ExportRadio = Radio as ExportRadioType
13 | ExportRadio.Group = Group
14 | 
15 | export default ExportRadio
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/radio/style.scss:
--------------------------------------------------------------------------------
 1 | .radio-wrapper {
 2 |   @apply relative flex items-start;
 3 | }
 4 | 
 5 | .radio {
 6 |   @apply flex items-center h-5;
 7 | }
 8 | 
 9 | .radio-input {
10 |   @apply h-4 w-4 text-blue-700 border-gray-300 focus:ring-blue-600;
11 | }
12 | 
13 | .radio-input-checked {
14 | }
15 | 
16 | .radio-description {
17 |   @apply ml-3 sm:text-sm font-medium text-slate-700;
18 | }
19 | 
20 | .radio-group {
21 |   @apply space-y-4;
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/rate/style.scss:
--------------------------------------------------------------------------------
1 | .rate {
2 |   @apply flex items-center;
3 | }
4 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/slider/style.scss:
--------------------------------------------------------------------------------
 1 | .slider {
 2 |   @apply relative w-full h-3 py-2.5;
 3 | }
 4 | 
 5 | .slider-progress {
 6 |   @apply h-0.5 bg-slate-100 rounded-sm;
 7 | }
 8 | 
 9 | .slider-progress-track {
10 |   @apply relative h-full mt-0 bg-blue-700;
11 | 
12 |   &:after {
13 |     @apply absolute top-1/2 -mt-1.5 -right-1.5 w-3 h-3 bg-blue-700 rounded-full;
14 |     content: '';
15 |   }
16 | }
17 | 
18 | .slider-input {
19 |   @apply absolute inset-0 opacity-0 w-full h-full cursor-pointer appearance-none;
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/spin/style.scss:
--------------------------------------------------------------------------------
 1 | .spin {
 2 |   @apply w-5 h-5 animate-spin;
 3 | }
 4 | 
 5 | .spin-circle {
 6 |   @apply opacity-25;
 7 | }
 8 | 
 9 | .spin-path {
10 |   @apply opacity-75;
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/style.scss:
--------------------------------------------------------------------------------
 1 | @import "avatar/style";
 2 | @import "badge/style";
 3 | @import "button/style";
 4 | @import "checkbox/style";
 5 | @import "color-picker/style";
 6 | @import "date-picker/style";
 7 | @import "dropdown/style";
 8 | @import "empty-states/style";
 9 | @import "form/style";
10 | @import "heading/style";
11 | @import "radio/style";
12 | @import "rate/style";
13 | @import "input/style";
14 | @import "menu/style";
15 | @import "modal/style";
16 | @import "navbar/style";
17 | @import "notification/style";
18 | @import "popup/style";
19 | @import "select/style";
20 | @import "slider/style";
21 | @import "spin/style";
22 | @import "stepper/style";
23 | @import "switch/style";
24 | @import "table/style";
25 | @import "tabs/style";
26 | @import "tooltip/style";
27 | @import "progress/style";
28 | @import "loader/style";
29 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/switch/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { SwitchGroupProps } from './Group'
 4 | import Group from './Group'
 5 | import type { SwitchProps } from './Switch'
 6 | import Switch from './Switch'
 7 | 
 8 | type ExportCheckboxType = FC<SwitchProps> & {
 9 |   Group: FC<SwitchGroupProps>
10 | }
11 | 
12 | const ExportSwitch = Switch as ExportCheckboxType
13 | ExportSwitch.Group = Group
14 | 
15 | export default ExportSwitch
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/tabs/index.ts:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { TabsPaneProps } from './Pane'
 4 | import Pane from './Pane'
 5 | import type { TabsProps } from './Tabs'
 6 | import Tabs from './Tabs'
 7 | 
 8 | type ExportTabsType = FC<TabsProps> & {
 9 |   Pane: FC<TabsPaneProps>
10 | }
11 | 
12 | const ExportTabs = Tabs as ExportTabsType
13 | ExportTabs.Pane = Pane
14 | 
15 | export default ExportTabs
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/tooltip/style.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 |   @apply px-2.5 py-1.5 z-50 text-center text-xs text-white break-words whitespace-pre pointer-events-none rounded;
3 |   text-decoration: none;
4 |   text-shadow: none;
5 |   text-transform: none;
6 |   letter-spacing: normal;
7 |   background: #1f1f1f;
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/webapp/src/components/ui/typing.ts:
--------------------------------------------------------------------------------
 1 | import type { HTMLAttributes, ReactNode } from 'react'
 2 | 
 3 | export type IComponentProps<E = HTMLElement> = HTMLAttributes<E>
 4 | 
 5 | export type IMapType<V = any> = Record<string | number | symbol, V>
 6 | 
 7 | export type IOptionType = IMapType<any> & {
 8 |   label: ReactNode
 9 |   value: any
10 |   disabled?: boolean
11 | }
12 | 
13 | export type IOptionGroupType = {
14 |   group: ReactNode
15 |   children: IOptionType[]
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/webapp/src/consts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './date'
2 | export * from './environments'
3 | export * from './formBuilder'
4 | export * from './formSettings'
5 | export * from './graphql'
6 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/compose.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum } from '@heyform-inc/shared-types-enums'
 2 | import type { ReactNode } from 'react'
 3 | 
 4 | export interface FieldItemProps {
 5 |   kind: FieldKindEnum
 6 |   icon: ReactNode
 7 |   label: string
 8 |   description: string
 9 | }
10 | 
11 | export interface ComponentsOptions {
12 |   name: string
13 |   children: FieldItemProps[]
14 | }
15 | 
16 | export enum ComposeTabKeyEnum {
17 |   COMPONENT = 'component',
18 |   THEME = 'theme',
19 |   CUSTOMIZE = 'customize'
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/form.ts:
--------------------------------------------------------------------------------
 1 | import type { FormField as IFormField } from '@heyform-inc/shared-types-enums'
 2 | import { Property } from '@heyform-inc/shared-types-enums'
 3 | 
 4 | export enum IntegrationStatusEnum {
 5 |   ACTIVE = 1,
 6 |   DISABLED
 7 | }
 8 | 
 9 | export interface FormIntegration {
10 |   formId: string
11 |   thirdPartyId: string
12 |   subKind: string
13 |   uniqueName: string
14 |   attributes?: Record<string, any>
15 |   status: IntegrationStatusEnum
16 | }
17 | 
18 | export interface FormAnalyticsSummary {
19 |   totalVisits: number
20 |   submissionCount: number
21 |   completeRate: number
22 |   averageDuration: string
23 | }
24 | 
25 | export interface FormField extends IFormField {
26 |   isCollapsed?: boolean
27 |   parent?: IFormField
28 |   properties?: Omit<Property, 'fields'> & {
29 |     fields?: FormField[]
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/index.ts:
--------------------------------------------------------------------------------
1 | export type { FormModel, SubmissionModel } from '@heyform-inc/shared-types-enums'
2 | export * from './project'
3 | export * from './user'
4 | export * from './workspace'
5 | export * from './form'
6 | export * from './template'
7 | export * from './integration'
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/integration.ts:
--------------------------------------------------------------------------------
 1 | export interface AppModel {
 2 |   id: string
 3 |   uniqueId: string
 4 |   category: string
 5 |   name: string
 6 |   description?: string
 7 |   avatar?: string
 8 |   homepage?: string
 9 |   helpLinkUrl?: string
10 |   attributes?: IMapType
11 |   integration?: IntegrationModel
12 | }
13 | 
14 | export interface IntegrationModel {
15 |   appId: string
16 |   attributes?: IMapType
17 |   formId: string
18 |   status: number
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/project.ts:
--------------------------------------------------------------------------------
 1 | export interface ProjectModel {
 2 |   id: string
 3 |   teamId: string
 4 |   name: string
 5 |   ownerId: string
 6 |   members: string[]
 7 |   formCount: number
 8 |   isOwner?: boolean
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/template.ts:
--------------------------------------------------------------------------------
1 | import type { FormModel } from '@heyform-inc/shared-types-enums'
2 | 
3 | export interface TemplateModal
4 |   extends Pick<FormModel, 'id' | 'name' | 'interactiveMode' | 'kind' | 'fields' | 'themeSettings'> {
5 |   category: string
6 |   published: boolean
7 | }
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/user.ts:
--------------------------------------------------------------------------------
 1 | export interface UserModel {
 2 |   id: string
 3 |   name: string
 4 |   email: string
 5 |   phoneNumber: string
 6 |   avatar: string
 7 |   note: string
 8 |   lastSeenAt?: number
 9 |   isSocialAccount?: boolean
10 |   isEmailVerified?: boolean
11 |   isDeletionScheduled?: boolean
12 |   deletionScheduledAt?: number
13 |   status: number
14 |   createdAt: string
15 |   updatedAt: string
16 | 
17 |   // Only for project members
18 |   isAssigned?: boolean
19 |   isOwner?: boolean
20 |   isSelf?: boolean
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/models/workspace.ts:
--------------------------------------------------------------------------------
 1 | import { ProjectModel } from '@/models/project'
 2 | import { UserModel } from '@/models/user'
 3 | 
 4 | export interface WorkspaceModel {
 5 |   id: string
 6 |   name: string
 7 |   ownerId: string
 8 |   avatar?: string
 9 |   enableCustomDomain?: boolean
10 |   inviteCode: string
11 |   inviteCodeExpireAt?: number
12 |   allowJoinByInviteLink: boolean
13 |   storageQuota: number
14 |   memberCount: number
15 |   additionalSeats: number
16 |   projects: ProjectModel[]
17 |   members: UserModel[]
18 |   isOwner?: boolean
19 |   owner?: UserModel
20 |   createdAt?: number
21 | }
22 | 
23 | export interface WorkspaceMemberModel {
24 |   id: string
25 |   name: string
26 |   email: string
27 |   avatar: string
28 |   isOwner: boolean
29 |   lastSeenAt?: number
30 | }
31 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Analytics/index.tsx:
--------------------------------------------------------------------------------
 1 | import { FC } from 'react'
 2 | 
 3 | import Report from './views/Report'
 4 | import Summary from './views/Summary'
 5 | 
 6 | const Analytics: FC = () => {
 7 |   return (
 8 |     <div className="form-content-container">
 9 |       <div className="container mx-auto max-w-5xl pt-14">
10 |         <div className="mx-4 md:mx-0">
11 |           <Summary />
12 |           <Report />
13 |         </div>
14 |       </div>
15 |     </div>
16 |   )
17 | }
18 | 
19 | export default Analytics
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/consts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './country'
2 | export * from './date'
3 | export * from './layout'
4 | export * from './rating'
5 | export * from './logic'
6 | export * from './payment'
7 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/consts/logic.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum } from '@heyform-inc/shared-types-enums'
 2 | 
 3 | import { NumberVariableIcon, StringVariableIcon } from '@/components'
 4 | import { type FieldConfig } from '@/pages/form/Create/views/FieldConfig'
 5 | 
 6 | export const VARIABLE_KIND_CONFIGS: FieldConfig[] = [
 7 |   {
 8 |     kind: 'number' as FieldKindEnum,
 9 |     icon: NumberVariableIcon,
10 |     label: 'formBuilder.variable.number',
11 |     textColor: '#06a17e',
12 |     backgroundColor: 'rgba(6,161,126,0.05)'
13 |   },
14 |   {
15 |     kind: 'string' as FieldKindEnum,
16 |     icon: StringVariableIcon,
17 |     label: 'formBuilder.variable.string',
18 |     textColor: '#06a17e',
19 |     backgroundColor: 'rgba(6,161,126,0.05)'
20 |   }
21 | ]
22 | 
23 | export const VARIABLE_INPUT_TYPES: any = {
24 |   number: 'number',
25 |   string: 'text'
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/store/hook.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | 
3 | import type { IContext } from './context'
4 | import { StoreContext } from './context'
5 | 
6 | export function useStoreContext(): IContext {
7 |   return useContext(StoreContext)
8 | }
9 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/store/index.ts:
--------------------------------------------------------------------------------
1 | export * from './context'
2 | export * from './hook'
3 | export * from './actions'
4 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './field'
2 | export * from './logic'
3 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/Country.tsx:
--------------------------------------------------------------------------------
 1 | import { IconChevronRight } from '@tabler/icons-react'
 2 | import type { FC } from 'react'
 3 | import { useTranslation } from 'react-i18next'
 4 | 
 5 | import { FakeSubmit } from '@/pages/form/Create/views/Compose/FakeSubmit'
 6 | 
 7 | import { FakeSelect } from '../FakeSelect'
 8 | import type { BlockProps } from './Block'
 9 | import { Block } from './Block'
10 | 
11 | export const Country: FC<BlockProps> = ({ field, locale, ...restProps }) => {
12 |   const { t } = useTranslation()
13 | 
14 |   return (
15 |     <Block className="heyform-country" field={field} locale={locale} {...restProps}>
16 |       <FakeSelect />
17 |       <FakeSubmit text={t('Next', { lng: locale })} icon={<IconChevronRight />} />
18 |     </Block>
19 |   )
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/LegalTerms.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useTranslation } from 'react-i18next'
 3 | 
 4 | import { FakeRadio } from '../FakeRadio'
 5 | import type { BlockProps } from './Block'
 6 | import { Block } from './Block'
 7 | 
 8 | export const LegalTerms: FC<BlockProps> = ({ field, locale, ...restProps }) => {
 9 |   const { t } = useTranslation()
10 | 
11 |   return (
12 |     <Block className="heyform-legal-terms" field={field} locale={locale} {...restProps}>
13 |       <div className="heyform-radio-group w-56">
14 |         <FakeRadio hotkey="Y" label={t('I accept', { lng: locale })} />
15 |         <FakeRadio hotkey="N" label={t("I don't accept", { lng: locale })} />
16 |       </div>
17 |     </Block>
18 |   )
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/Rating.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { Rate } from '@/components/ui'
 4 | import { RATING_SHAPE_CONFIG } from '@/pages/form/Create/consts'
 5 | 
 6 | import type { BlockProps } from './Block'
 7 | import { Block } from './Block'
 8 | 
 9 | export const Rating: FC<BlockProps> = ({ field, locale, ...restProps }) => {
10 |   function characterRender(index: number) {
11 |     return (
12 |       <>
13 |         {RATING_SHAPE_CONFIG[field.properties?.shape || 'star']}
14 |         <span className="heyform-rate-index">{index}</span>
15 |       </>
16 |     )
17 |   }
18 | 
19 |   return (
20 |     <Block className="heyform-rating" field={field} locale={locale} {...restProps}>
21 |       <Rate count={field.properties?.total || 5} itemRender={characterRender} />
22 |     </Block>
23 |   )
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/Statement.tsx:
--------------------------------------------------------------------------------
 1 | import { IconChevronRight } from '@tabler/icons-react'
 2 | import type { FC } from 'react'
 3 | import { useTranslation } from 'react-i18next'
 4 | 
 5 | import { FakeSubmit } from '@/pages/form/Create/views/Compose/FakeSubmit'
 6 | 
 7 | import type { BlockProps } from './Block'
 8 | import { Block } from './Block'
 9 | 
10 | export const Statement: FC<BlockProps> = ({ field, locale, ...restProps }) => {
11 |   const { t } = useTranslation()
12 | 
13 |   return (
14 |     <Block className="heyform-statement" field={field} locale={locale} {...restProps}>
15 |       <FakeSubmit
16 |         text={field.properties?.buttonText || t('Next', { lng: locale })}
17 |         icon={<IconChevronRight />}
18 |       />
19 |     </Block>
20 |   )
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/ThankYou.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useTranslation } from 'react-i18next'
 3 | 
 4 | import type { BlockProps } from './Block'
 5 | import { Block } from './Block'
 6 | 
 7 | export const ThankYou: FC<BlockProps> = ({ field, locale, className, children, ...restProps }) => {
 8 |   const { t } = useTranslation()
 9 | 
10 |   return (
11 |     <Block
12 |       className="heyform-thank-you heyform-empty-state"
13 |       field={field}
14 |       locale={locale}
15 |       {...restProps}
16 |     />
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/Welcome.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useTranslation } from 'react-i18next'
 3 | 
 4 | import { FakeSubmit } from '@/pages/form/Create/views/Compose/FakeSubmit'
 5 | 
 6 | import type { BlockProps } from './Block'
 7 | import { Block } from './Block'
 8 | 
 9 | export const Welcome: FC<BlockProps> = ({ field, locale, ...restProps }) => {
10 |   const { t } = useTranslation()
11 | 
12 |   return (
13 |     <Block
14 |       className="heyform-welcome heyform-empty-state"
15 |       field={field}
16 |       locale={locale}
17 |       {...restProps}
18 |     >
19 |       <FakeSubmit text={field.properties?.buttonText || t('Next', { lng: locale })} />
20 |     </Block>
21 |   )
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/YesNo.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useTranslation } from 'react-i18next'
 3 | 
 4 | import { FakeRadio } from '../FakeRadio'
 5 | import type { BlockProps } from './Block'
 6 | import { Block } from './Block'
 7 | 
 8 | export const YesNo: FC<BlockProps> = ({ field, locale, ...restProps }) => {
 9 |   const { t } = useTranslation()
10 | 
11 |   return (
12 |     <Block className="heyform-yes-no" field={field} locale={locale} {...restProps}>
13 |       <div className="heyform-radio-group w-40">
14 |         <FakeRadio hotkey="Y" label={t('Yes', { lng: locale })} />
15 |         <FakeRadio hotkey="N" label={t('No', { lng: locale })} />
16 |       </div>
17 |     </Block>
18 |   )
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/Blocks/index.tsx:
--------------------------------------------------------------------------------
 1 | export * from './Address'
 2 | export * from './Country'
 3 | export * from './Date'
 4 | export * from './DateRange'
 5 | export * from './Email'
 6 | export * from './FileUpload'
 7 | export * from './FullName'
 8 | export * from './InputTable'
 9 | export * from './LegalTerms'
10 | export * from './LongText'
11 | export * from './MultipleChoice'
12 | export * from './Number'
13 | export * from './OpinionScale'
14 | export * from './PhoneNumber'
15 | export * from './PictureChoice'
16 | export * from './Rating'
17 | export * from './ShortText'
18 | export * from './Signature'
19 | export * from './Statement'
20 | export * from './ThankYou'
21 | export * from './Website'
22 | export * from './Welcome'
23 | export * from './YesNo'
24 | export * from './Payment'
25 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/FakeRadio.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | interface FakeRadioProps extends IComponentProps {
 4 |   hotkey?: string
 5 |   label: string | number
 6 | }
 7 | 
 8 | export const FakeRadio: FC<FakeRadioProps> = ({ hotkey, label, ...restProps }) => {
 9 |   return (
10 |     <div className="heyform-radio" {...restProps}>
11 |       <div className="heyform-radio-container">
12 |         <div className="heyform-radio-content">
13 |           {hotkey && <div className="heyform-radio-hotkey">{hotkey}</div>}
14 |           <div className="heyform-radio-label">{label}</div>
15 |         </div>
16 |       </div>
17 |     </div>
18 |   )
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/FakeSelect.tsx:
--------------------------------------------------------------------------------
 1 | import { IconChevronDown } from '@tabler/icons-react'
 2 | import type { FC } from 'react'
 3 | 
 4 | export const FakeSelect: FC<IComponentProps> = ({ ...restProps }) => {
 5 |   return (
 6 |     <div className="heyform-select" {...restProps}>
 7 |       <div className="heyform-select-container">
 8 |         <span className="heyform-select-value" />
 9 |         <span className="heyform-select-arrow-icon">
10 |           <IconChevronDown />
11 |         </span>
12 |       </div>
13 |     </div>
14 |   )
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/FakeSubmit.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC, ReactNode } from 'react'
 2 | 
 3 | interface FakeSubmitProps extends IComponentProps {
 4 |   text?: string
 5 |   icon?: ReactNode
 6 | }
 7 | 
 8 | export const FakeSubmit: FC<FakeSubmitProps> = ({ text, icon, ...restProps }) => {
 9 |   return (
10 |     <div className="heyform-submit-container" {...restProps}>
11 |       <div className="heyform-submit-button">
12 |         <span>{text}</span>
13 |         {icon}
14 |       </div>
15 |     </div>
16 |   )
17 | }
18 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/Compose/FlagIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | interface FlagIconProps {
 4 |   countryCode?: string
 5 | }
 6 | 
 7 | export const FlagIcon: FC<FlagIconProps> = ({ countryCode = 'US' }) => {
 8 |   return <span className={`flag-icon flag-icon-${countryCode?.toLowerCase()}`} />
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/LeftSidebar/FieldKindIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { questionNumber } from '@/pages/form/views/FormComponents'
 4 | 
 5 | import type { FieldIconProps } from '../FieldIcon'
 6 | import { FieldIcon } from '../FieldIcon'
 7 | 
 8 | interface FieldKindIconProps extends FieldIconProps {
 9 |   parentIndex?: number
10 | }
11 | 
12 | export const FieldKindIcon: FC<FieldKindIconProps> = ({ parentIndex, index, ...restProps }) => {
13 |   return (
14 |     <FieldIcon
15 |       className="field-card-icon"
16 |       index={questionNumber(index, parentIndex)}
17 |       iconOnly={false}
18 |       {...restProps}
19 |     />
20 |   )
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/LogicFlow/index.tsx:
--------------------------------------------------------------------------------
 1 | import { ReactFlowProvider } from 'react-flow-renderer'
 2 | 
 3 | import { EdgeArrow } from '@/components'
 4 | 
 5 | import { Flow } from './Flow'
 6 | 
 7 | export const LogicFlow = () => {
 8 |   return (
 9 |     <div className="logic-flow">
10 |       <EdgeArrow />
11 |       <ReactFlowProvider>
12 |         <Flow />
13 |       </ReactFlowProvider>
14 |     </div>
15 |   )
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/RightSidebar/Design/style.scss:
--------------------------------------------------------------------------------
 1 | .theme-list {
 2 |   height: calc(100vh - 176px);
 3 | }
 4 | 
 5 | .customize-bottom {
 6 |   @apply mb-0 p-4 border-t border-gray-200;
 7 | }
 8 | 
 9 | .customize-list {
10 |   height: calc(100vh - 176px - 74px);
11 | 
12 |   .right-sidebar-settings-item {
13 |     @apply py-0;
14 |   }
15 | }
16 | 
17 | button.custom-css-help {
18 |   @apply ml-2 p-1;
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Create/views/RightSidebar/Logic/index.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { Rules } from './Rules'
 4 | import { Variables } from './Variables'
 5 | 
 6 | export const Logic: FC = () => {
 7 |   return (
 8 |     <div>
 9 |       <Variables />
10 |       <Rules />
11 |     </div>
12 |   )
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Render/utils/payment.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum, FormModel } from '@heyform-inc/shared-types-enums'
 2 | import { helper } from '@heyform-inc/utils'
 3 | 
 4 | export function isStripeEnabled(form: any): boolean {
 5 |   return helper.isValid(form.stripe?.accountId) && !!getPaymentField(form)
 6 | }
 7 | 
 8 | export function getPaymentField(form: FormModel) {
 9 |   return form.fields?.find(f => f.kind === FieldKindEnum.PAYMENT)
10 | }
11 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/ExportLink.tsx:
--------------------------------------------------------------------------------
 1 | import { IconDownload } from '@tabler/icons-react'
 2 | import { FC } from 'react'
 3 | import { useTranslation } from 'react-i18next'
 4 | 
 5 | import { Button } from '@/components/ui'
 6 | import { useParam } from '@/utils'
 7 | 
 8 | export const ExportLink: FC = () => {
 9 |   const { t } = useTranslation()
10 |   const { formId } = useParam()
11 | 
12 |   function handleClick() {
13 |     window.open(`/export/submissions?formId=${formId}`)
14 |   }
15 | 
16 |   return (
17 |     <Button
18 |       className="ml-5"
19 |       leading={<IconDownload className="h-6 w-6 text-slate-500" />}
20 |       onClick={handleClick}
21 |     >
22 |       <span className="ml-2">{t('submissions.export')}</span>
23 |     </Button>
24 |   )
25 | }
26 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/SheetHeaderCell.tsx:
--------------------------------------------------------------------------------
 1 | import { FC } from 'react'
 2 | 
 3 | import { SheetKindIcon } from './SheetKindIcon'
 4 | import { SheetHeaderCellProps } from './types'
 5 | 
 6 | export const SheetHeaderCell: FC<SheetHeaderCellProps> = ({ column }) => {
 7 |   return (
 8 |     <div className="heygrid-header-cell flex items-center bg-white">
 9 |       <SheetKindIcon className="mr-2 h-[22px] w-[22px] p-0.5" kind={column.kind!} />
10 |       <span className="h-full flex-1 truncate">{column.name}</span>
11 |     </div>
12 |   )
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/SheetKindIcon.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { CUSTOM_FIELDS_CONFIGS, FIELD_CONFIGS } from '@/pages/form/Create/views/FieldConfig'
 5 | 
 6 | import { SheetKindIconProps } from './types'
 7 | 
 8 | const configs = [...FIELD_CONFIGS, ...CUSTOM_FIELDS_CONFIGS]
 9 | 
10 | export const SheetKindIcon: FC<SheetKindIconProps> = ({ className, kind }) => {
11 |   const config = useMemo(() => configs.find(c => c.kind === kind)!, [kind])
12 | 
13 |   return (
14 |     <config.icon
15 |       className={clsx('rounded', className)}
16 |       style={{
17 |         backgroundColor: config?.backgroundColor,
18 |         color: config?.textColor
19 |       }}
20 |     />
21 |   )
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/formatters/ValueFormatter.tsx:
--------------------------------------------------------------------------------
 1 | import type { FormatterProps } from '../types'
 2 | 
 3 | export function ValueFormatter<R, SR>(props: FormatterProps<R, SR>) {
 4 |   try {
 5 |     return <>{props.row[props.column.key as keyof R]}</>
 6 |   } catch {
 7 |     return null
 8 |   }
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/formatters/index.ts:
--------------------------------------------------------------------------------
1 | export * from '../formatters/SelectCellFormatter'
2 | export * from '../formatters/ValueFormatter'
3 | export * from '../formatters/ToggleGroupFormatter'
4 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useGridDimensions'
2 | export * from './useViewportColumns'
3 | export * from './useViewportRows'
4 | export * from './useFocusRef'
5 | export * from './useLatestFunc'
6 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/hooks/useFocusRef.ts:
--------------------------------------------------------------------------------
 1 | import { useLayoutEffect, useRef } from 'react'
 2 | 
 3 | export function useFocusRef<T extends HTMLOrSVGElement>(isCellSelected: boolean | undefined) {
 4 |   const ref = useRef<T>(null)
 5 |   useLayoutEffect(() => {
 6 |     if (!isCellSelected) return
 7 |     ref.current?.focus({ preventScroll: true })
 8 |   }, [isCellSelected])
 9 | 
10 |   return ref
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/hooks/useLatestFunc.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect, useRef } from 'react'
 2 | 
 3 | // https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often
 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
 5 | export function useLatestFunc<T extends (...args: any[]) => any>(fn: T) {
 6 |   const ref = useRef(fn)
 7 | 
 8 |   useEffect(() => {
 9 |     ref.current = fn
10 |   })
11 | 
12 |   return useCallback((...args: Parameters<T>) => {
13 |     ref.current(...args)
14 |   }, [])
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/AddressCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const AddressCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.key]
 9 | 
10 |     if (helper.isObject(v)) {
11 |       return [v.address1, v.address2, v.city, v.state, v.zip].filter(Boolean).join(', ')
12 |     }
13 |   }, [column.key, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/DateRangeCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const DateRangeCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.key]
 9 | 
10 |     if (helper.isObject(v)) {
11 |       return [v?.start, v?.end].filter(Boolean).join('  -  ')
12 |     }
13 |   }, [column.key, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/DropPickerCell.tsx:
--------------------------------------------------------------------------------
 1 | import { FC } from 'react'
 2 | 
 3 | import { TagGroup } from '@/components'
 4 | 
 5 | import { SheetCellProps } from '../types'
 6 | 
 7 | export const DropPickerCell: FC<SheetCellProps> = ({ column, row }) => {
 8 |   const value = row[column.key]
 9 |   const choice = column.properties?.choices?.find(choice => choice.id === value)
10 | 
11 |   return (
12 |     <>
13 |       {choice && (
14 |         <div className="heygrid-cell-text flex items-center overflow-hidden text-ellipsis whitespace-nowrap">
15 |           <TagGroup className="px-4" tags={[choice]} />
16 |         </div>
17 |       )}
18 |     </>
19 |   )
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/FullNameCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const FullNameCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.key]
 9 | 
10 |     if (helper.isObject(v)) {
11 |       return [v.firstName, v.lastName].filter(Boolean).join(', ')
12 |     }
13 |   }, [column.key, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/HiddenFieldCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const HiddenFieldCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.name as string]
 9 | 
10 |     if (helper.isString(v) || helper.isNumber(v)) {
11 |       return v
12 |     }
13 |   }, [column.name, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/OpinionScaleCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const OpinionScaleCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.key]
 9 | 
10 |     return [helper.isString(v) || helper.isNumber(v) ? v : null, column.properties?.total]
11 |       .filter(helper.isValid)
12 |       .join(' / ')
13 |   }, [column.key, column.properties?.total, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/SignatureCell.tsx:
--------------------------------------------------------------------------------
 1 | import { FC, useMemo } from 'react'
 2 | 
 3 | import { getUrlValue } from '@/utils'
 4 | 
 5 | import { SheetCellProps } from '../types'
 6 | 
 7 | export const SignatureCell: FC<SheetCellProps> = ({ column, row }) => {
 8 |   const value = useMemo(() => getUrlValue(row[column.key]), [column.key, row])
 9 | 
10 |   return (
11 |     <div className="heygrid-cell-text flex h-10 items-center overflow-hidden px-4 leading-[1px]">
12 |       {value && <img className="h-5 w-11 object-cover" src={value} width={80} height={40} />}
13 |     </div>
14 |   )
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/SubmitDateCell.tsx:
--------------------------------------------------------------------------------
 1 | import { unixDate } from '@heyform-inc/utils'
 2 | import { FC } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const SubmitDateCell: FC<SheetCellProps> = ({ row }) => {
 7 |   const value: number = row.endAt ?? 0
 8 | 
 9 |   return (
10 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">
11 |       {unixDate(value).format('MMM DD, YYYY')}
12 |     </div>
13 |   )
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/TextCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { SheetCellProps } from '../types'
 5 | 
 6 | export const TextCell: FC<SheetCellProps> = ({ column, row }) => {
 7 |   const value = useMemo(() => {
 8 |     const v = row[column.key]
 9 | 
10 |     if (helper.isString(v) || helper.isNumber(v)) {
11 |       return v
12 |     }
13 |   }, [column.key, row])
14 | 
15 |   return (
16 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">{value}</div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/sheetCells/UrlCell.tsx:
--------------------------------------------------------------------------------
 1 | import { helper } from '@heyform-inc/utils'
 2 | import { FC, useMemo } from 'react'
 3 | 
 4 | import { getUrlValue } from '@/utils'
 5 | 
 6 | import { SheetCellProps } from '../types'
 7 | 
 8 | export const UrlCell: FC<SheetCellProps> = ({ column, row }) => {
 9 |   const value = useMemo(() => getUrlValue(row[column.key]), [column.key, row])
10 | 
11 |   return (
12 |     <div className="heygrid-cell-text overflow-hidden text-ellipsis whitespace-nowrap">
13 |       <a href={value} target="_blank" rel="noreferrer">
14 |         {value}
15 |       </a>
16 |     </div>
17 |   )
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/Submissions/views/Sheet/utils/index.ts:
--------------------------------------------------------------------------------
 1 | import type { CalculatedColumn } from '../types'
 2 | 
 3 | export * from '../utils/keyboardUtils'
 4 | export * from '../utils/selectedCellUtils'
 5 | 
 6 | export function assertIsValidKeyGetter<R>(
 7 |   keyGetter: unknown
 8 | ): asserts keyGetter is (row: R) => React.Key {
 9 |   if (typeof keyGetter !== 'function') {
10 |     throw new Error('Please specify the rowKeyGetter prop to use selection')
11 |   }
12 | }
13 | 
14 | export function getCellStyle<R, SR>(column: CalculatedColumn<R, SR>): React.CSSProperties {
15 |   return column.frozen
16 |     ? { left: `var(--frozen-left-${column.key})` }
17 |     : { gridColumnStart: column.idx + 1 }
18 | }
19 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/blocks/Statement.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import type { BlockProps } from './Block'
 4 | import { Block } from './Block'
 5 | import { Form } from './Form'
 6 | 
 7 | export const Statement: FC<BlockProps> = ({ field, ...restProps }) => {
 8 |   return (
 9 |     <Block className="heyform-statement heyform-empty-state" field={field} {...restProps}>
10 |       <Form field={field} />
11 |     </Block>
12 |   )
13 | }
14 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/blocks/Welcome.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { useStore } from '../store'
 4 | import { Branding } from '../views/Branding'
 5 | import type { BlockProps } from './Block'
 6 | import { EmptyState } from './EmptyState'
 7 | 
 8 | export const Welcome: FC<BlockProps> = ({ field, ...restProps }) => {
 9 |   const { dispatch } = useStore()
10 | 
11 |   function handleClick() {
12 |     dispatch({
13 |       type: 'setIsStarted',
14 |       payload: {
15 |         isStarted: true
16 |       }
17 |     })
18 |   }
19 | 
20 |   return (
21 |     <>
22 |       <EmptyState {...restProps} className="heyform-welcome" field={field} onClick={handleClick} />
23 |       <Branding />
24 |     </>
25 |   )
26 | }
27 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/components/FlagIcon.tsx:
--------------------------------------------------------------------------------
 1 | import clsx from 'clsx'
 2 | import type { FC } from 'react'
 3 | 
 4 | import type { IComponentProps } from '@/components/ui/typing'
 5 | 
 6 | export interface FlagIconProps extends IComponentProps {
 7 |   countryCode?: string
 8 | }
 9 | 
10 | export const FlagIcon: FC<FlagIconProps> = ({ className, countryCode = 'US' }) => {
11 |   return <span className={clsx(`fi fi-${countryCode?.toLowerCase()}`, className)} />
12 | }
13 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/components/Icons/CollapseIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '@/components/ui/typing'
 4 | 
 5 | export const CollapseIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => (
 6 |   <svg width="8" height="6" viewBox="0 0 8 6" xmlns="http://www.w3.org/2000/svg" {...props}>
 7 |     <path d="M4 6l4-6H0z" fillRule="evenodd" fill="currentColor"></path>
 8 |   </svg>
 9 | )
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/components/Icons/StarIcon.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | 
 3 | import { IComponentProps } from '@/components/ui/typing'
 4 | 
 5 | export const StarIcon: FC<IComponentProps<HTMLOrSVGElement>> = props => {
 6 |   return (
 7 |     <svg
 8 |       width="24"
 9 |       height="24"
10 |       viewBox="0 0 24 24"
11 |       fill="none"
12 |       xmlns="http://www.w3.org/2000/svg"
13 |       className="heyform-icon"
14 |       {...props}
15 |     >
16 |       <path
17 |         d="M11.9993 2.5L8.9428 8.7388L2 9.74555L7.02945 14.6625L5.8272 21.5L11.9993 18.2096L18.1727 21.5L16.9793 14.6625L22 9.74555L15.0956 8.7388L11.9993 2.5Z"
18 |         className="heyform-icon-fill heyform-icon-stroke"
19 |         strokeWidth="1"
20 |         strokeLinejoin="round"
21 |       />
22 |     </svg>
23 |   )
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/components/Icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CollapseIcon'
2 | export * from './CrownIcon'
3 | export * from './EmotionIcon'
4 | export * from './LikeIcon'
5 | export * from './LogoIcon'
6 | export * from './StarIcon'
7 | export * from './ThumbsUpIcon'
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/components/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './CountrySelect'
 2 | export * from './DateInput'
 3 | export * from './DateRangeInput'
 4 | export * from './FileUploader'
 5 | export * from './FlagIcon'
 6 | export * from './FormField'
 7 | export * from './Icons'
 8 | export * from './Input'
 9 | export * from './Layout'
10 | export * from './PhoneNumberInput'
11 | export * from './Radio'
12 | export * from './RadioGroup'
13 | export * from './SelectHelper'
14 | export * from './SignaturePad'
15 | export * from './Slide'
16 | export * from './Submit'
17 | export * from './TableInput'
18 | export * from './TemporaryError'
19 | export * from './Textarea'
20 | export * from './ChoiceRadio'
21 | export * from './ChoiceRadioGroup'
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/consts/fileUpload.ts:
--------------------------------------------------------------------------------
 1 | export const ACCEPTED_FILE_MIMES = [
 2 |   'image/jpeg',
 3 |   'image/png',
 4 |   'image/bmp',
 5 |   'image/gif',
 6 |   'text/plain',
 7 |   'text/markdown',
 8 |   'application/msword',
 9 |   'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
10 |   'application/vnd.ms-excel',
11 |   'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
12 |   'text/csv',
13 |   'application/vnd.ms-powerpoint',
14 |   'application/vnd.openxmlformats-officedocument.presentationml.presentation',
15 |   'application/pdf',
16 |   'video/mp4',
17 |   'video/x-ms-wmv'
18 | ]
19 | 
20 | export const MAX_FILE_SIZE = '10MB'
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/consts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './country'
2 | export * from './date'
3 | export * from './fileUpload'
4 | export * from './motion'
5 | export * from './payment'
6 | export * from './rating'
7 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/consts/motion.ts:
--------------------------------------------------------------------------------
1 | export const MOTION_UNMOUNTED_STATES = ['exited', 'unmounted']
2 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/consts/payment.ts:
--------------------------------------------------------------------------------
 1 | import { IMapType } from '@/components/ui/typing'
 2 | 
 3 | export const CURRENCY_SYMBOLS: IMapType = {
 4 |   EUR: '\u20ac',
 5 |   GBP: '\xa3',
 6 |   USD: '
#39;,
 7 |   AUD: 'A
#39;,
 8 |   CAD: 'CA
#39;,
 9 |   CHF: 'CHF ',
10 |   NOK: 'NOK ',
11 |   SEK: 'SEK ',
12 |   DKK: 'DKK ',
13 |   MXN: 'MX
#39;,
14 |   NZD: 'NZ
#39;,
15 |   BRL: 'R
#39;
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/consts/rating.tsx:
--------------------------------------------------------------------------------
 1 | import { IMapType } from '@/components/ui/typing'
 2 | 
 3 | import { CrownIcon, EmotionIcon, LikeIcon, StarIcon, ThumbsUpIcon } from '../components/Icons'
 4 | 
 5 | export const RATING_SHAPE_ICONS: IMapType = {
 6 |   heart: <LikeIcon />,
 7 |   thumb_up: <ThumbsUpIcon />,
 8 |   happy: <EmotionIcon />,
 9 |   crown: <CrownIcon />,
10 |   star: <StarIcon />
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/index.ts:
--------------------------------------------------------------------------------
1 | export * from './consts/payment'
2 | export { default as locales } from './locales'
3 | export * from './Renderer'
4 | export * from './theme'
5 | export * from './utils'
6 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/locales/index.ts:
--------------------------------------------------------------------------------
 1 | import de from './de'
 2 | import en from './en'
 3 | import es from './es'
 4 | import fr from './fr'
 5 | import pl from './pl'
 6 | import ptBr from './pt-br'
 7 | import tr from './tr'
 8 | import zhCn from './zh-cn'
 9 | import zhTw from './zh-tw'
10 | import cs from './cs'
11 | 
12 | export default {
13 |   en: {
14 |     translation: en
15 |   },
16 |   fr: {
17 |     translation: fr
18 |   },
19 |   de: {
20 |     translation: de
21 |   },
22 |   es: {
23 |     translation: es
24 |   },
25 |   pl: {
26 |     translation: pl
27 |   },
28 |   'pt-br': {
29 |     translation: ptBr
30 |   },
31 |   tr: {
32 |     translation: tr
33 |   },
34 |   'zh-cn': {
35 |     translation: zhCn
36 |   },
37 |   'zh-tw': {
38 |     translation: zhTw
39 |   },
40 |   cs: {
41 |     translation: cs
42 |   },
43 | }
44 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/typings.ts:
--------------------------------------------------------------------------------
 1 | import { FieldKindEnum, FormField, FormModel } from '@heyform-inc/shared-types-enums'
 2 | 
 3 | export interface IFormField extends FormField {
 4 |   parent?: IFormField
 5 |   isTouched?: boolean
 6 | }
 7 | 
 8 | export interface IFormModel extends FormModel {
 9 |   fields: IFormField[]
10 | }
11 | 
12 | export interface IPartialFormField {
13 |   id: string
14 |   index: number
15 |   title?: string | any[]
16 |   kind: FieldKindEnum
17 |   required?: boolean
18 |   children?: IPartialFormField[]
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/utils/hook.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect } from 'react'
 2 | import { useTranslation as useReactTranslation } from 'react-i18next'
 3 | 
 4 | import { IMapType } from '@/components/ui/typing'
 5 | 
 6 | import { useStore } from '../store'
 7 | 
 8 | export function useTranslation(overrideLng?: string) {
 9 |   const { t: _t, i18n } = useReactTranslation()
10 |   const { state } = useStore()
11 | 
12 |   const t = useCallback((key: string, options?: IMapType) => {
13 |     return _t(key, {
14 |       ...options,
15 |       lng: overrideLng || state.locale
16 |     })
17 |   }, [])
18 | 
19 |   return { t, i18n }
20 | }
21 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './form'
2 | export * from './hook'
3 | export * from './lru'
4 | export * from './message'
5 | export * from './script'
6 | export { default as GlobalTimeout, Timeout } from './timeout'
7 | export * from './browser-language'
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/utils/message.ts:
--------------------------------------------------------------------------------
 1 | export function sendHideModalMessage() {
 2 |   window.parent?.postMessage(
 3 |     {
 4 |       source: 'HEYFORM',
 5 |       eventName: 'HIDE_EMBED_MODAL'
 6 |     },
 7 |     '*'
 8 |   )
 9 | }
10 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/utils/timeout.ts:
--------------------------------------------------------------------------------
 1 | import { IMapType } from '@/components/ui/typing'
 2 | 
 3 | interface TimeoutProps {
 4 |   name: string
 5 |   duration: number
 6 |   callback: () => void
 7 | }
 8 | 
 9 | export class Timeout {
10 |   private readonly caches: IMapType<number> = {}
11 | 
12 |   add({ name, duration, callback }: TimeoutProps) {
13 |     this.caches[name] = setTimeout(callback, duration)
14 |   }
15 | 
16 |   remove(name: string) {
17 |     const cache = this.caches[name]
18 | 
19 |     if (cache) {
20 |       clearTimeout(cache)
21 |       delete this.caches[name]
22 |     }
23 |   }
24 | 
25 |   clear() {
26 |     Object.keys(this.caches).forEach(key => {
27 |       this.remove(key)
28 |     })
29 |   }
30 | }
31 | 
32 | export default new Timeout()
33 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/form/views/FormComponents/views/Branding.tsx:
--------------------------------------------------------------------------------
 1 | import { FC } from 'react'
 2 | 
 3 | import { LogoIcon } from '../components'
 4 | import { useTranslation } from '../utils'
 5 | 
 6 | export const Branding: FC = () => {
 7 |   const { t } = useTranslation()
 8 | 
 9 |   return (
10 |     <a className="heyform-branding" href="https://heyform.net/?ref=badge" target="_blank">
11 |       <LogoIcon /> {t('Made with')} HeyForm
12 |     </a>
13 |   )
14 | }
15 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/project/Project/index.scss:
--------------------------------------------------------------------------------
1 | .forms {
2 |   tbody {
3 |     tr {
4 |       @apply cursor-pointer;
5 |     }
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/packages/webapp/src/pages/project/views/index.scss:
--------------------------------------------------------------------------------
 1 | .form-status {
 2 |   @apply text-sm text-slate-500 font-medium p-0 bg-transparent;
 3 | 
 4 |   &.badge-red {
 5 |     .badge-dot {
 6 |       @apply text-red-600;
 7 |     }
 8 |   }
 9 | 
10 |   &.badge-green {
11 |     .badge-dot {
12 |       @apply text-green-500;
13 |     }
14 |   }
15 | }
16 | 


--------------------------------------------------------------------------------
/packages/webapp/src/service/index.ts:
--------------------------------------------------------------------------------
 1 | export * from './auth.service'
 2 | export * from './form.service'
 3 | export * from './project.service'
 4 | export * from './unsplash.service'
 5 | export * from './user.service'
 6 | export * from './workspace.service'
 7 | export * from './app.service'
 8 | export * from './submission.service'
 9 | export * from './template.service'
10 | export * from './integration.service'
11 | export * from './payment.service'
12 | 


--------------------------------------------------------------------------------
/packages/webapp/src/service/unsplash.service.ts:
--------------------------------------------------------------------------------
 1 | import { UNSPLASH_SEARCH_GQL, UNSPLASH_TRACK_DOWNLOAD_GQL } from '@/consts'
 2 | import { request } from '@/utils'
 3 | 
 4 | export class UnsplashService {
 5 |   static async search(keyword?: string) {
 6 |     return request.query({
 7 |       query: UNSPLASH_SEARCH_GQL,
 8 |       variables: {
 9 |         input: {
10 |           keyword
11 |         }
12 |       },
13 |       fetchPolicy: 'network-only'
14 |     })
15 |   }
16 | 
17 |   static trackDownload(downloadUrl: string) {
18 |     return request.mutate({
19 |       mutation: UNSPLASH_TRACK_DOWNLOAD_GQL,
20 |       variables: {
21 |         input: {
22 |           downloadUrl
23 |         }
24 |       }
25 |     })
26 |   }
27 | }
28 | 


--------------------------------------------------------------------------------
/packages/webapp/src/store/app.store.ts:
--------------------------------------------------------------------------------
 1 | import { makeAutoObservable } from 'mobx'
 2 | 
 3 | export class AppStore {
 4 |   // Email address of user who wants to reset password
 5 |   resetPasswordEmail = ''
 6 | 
 7 |   // Sidebar is open or not
 8 |   isSidebarOpen = false
 9 | 
10 |   // Plan modal is open or not
11 |   isPlanModalOpen = false
12 | 
13 |   // Open create from modal
14 |   isCreateFormOpen = false
15 | 
16 |   // Form preview is open or not
17 |   isFormPreviewOpen = false
18 | 
19 |   // Form share modal is open or not
20 |   isFormShareModalOpen = false
21 | 
22 |   // Multi-language modal is open or not
23 |   isMultiLanguageModalOpen = false
24 | 
25 |   // User settings is open or not
26 |   isUserSettingsOpen = false
27 | 
28 |   constructor() {
29 |     makeAutoObservable(this)
30 |   }
31 | }
32 | 


--------------------------------------------------------------------------------
/packages/webapp/src/store/mobxStorage.ts:
--------------------------------------------------------------------------------
 1 | import { helper, pickValidValues } from '@heyform-inc/utils'
 2 | import { autorun, set, toJS } from 'mobx'
 3 | import store2 from 'store2'
 4 | 
 5 | export function mobxStorage(storeInstance: any, storeName: string, fields?: string[]) {
 6 |   const cache = store2.get(storeName)
 7 | 
 8 |   if (helper.isValid(cache)) {
 9 |     set(storeInstance, cache)
10 |   }
11 | 
12 |   autorun(() => {
13 |     let value = toJS(storeInstance)
14 | 
15 |     if (helper.isValid(fields)) {
16 |       value = pickValidValues(value, fields!)
17 |     }
18 | 
19 |     store2.set(storeName, value)
20 |   })
21 | }
22 | 


--------------------------------------------------------------------------------
/packages/webapp/src/store/user.store.ts:
--------------------------------------------------------------------------------
 1 | import { makeAutoObservable } from 'mobx'
 2 | 
 3 | import { UserModel } from '@/models'
 4 | 
 5 | import { mobxStorage } from './mobxStorage'
 6 | 
 7 | export class UserStore {
 8 |   user = {} as UserModel
 9 | 
10 |   constructor() {
11 |     makeAutoObservable(this)
12 |     mobxStorage(this, 'UserStore')
13 |   }
14 | 
15 |   setUser(user: UserModel) {
16 |     this.user = user
17 |   }
18 | 
19 |   update(updates: Partial<UserModel>) {
20 |     Object.assign(this.user, updates)
21 |   }
22 | }
23 | 


--------------------------------------------------------------------------------
/packages/webapp/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './auth'
2 | export * from './hook'
3 | export * from './request'
4 | export * from './helper'
5 | 


--------------------------------------------------------------------------------
/packages/webapp/tailwind.config.js:
--------------------------------------------------------------------------------
 1 | const defaultTheme = require('tailwindcss/defaultTheme')
 2 | 
 3 | module.exports = {
 4 |   content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
 5 |   theme: {
 6 |     fontFamily: {
 7 |       sans: ['Inter', ...defaultTheme.fontFamily.sans],
 8 |     },
 9 |     extend: {
10 |     }
11 |   },
12 |   variants: {
13 |     extend: {}
14 |   },
15 |   plugins: [require('@tailwindcss/forms')]
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/webapp/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "baseUrl": ".",
 4 |     "paths": {
 5 |       "@/*": ["src/*"]
 6 |     },
 7 |     "target": "ESNext",
 8 | 		"module": "esnext",
 9 | 		"moduleResolution": "node",
10 |     "lib": ["dom", "dom.iterable", "esnext"],
11 |     "types": ["vite/client"],
12 |     "allowJs": false,
13 |     "skipLibCheck": true,
14 | 		"esModuleInterop": true,
15 |     "allowSyntheticDefaultImports": true,
16 |     "strict": true,
17 |     "forceConsistentCasingInFileNames": true,
18 |     "noFallthroughCasesInSwitch": true,
19 |     "resolveJsonModule": true,
20 |     "isolatedModules": true,
21 |     "noEmit": true,
22 |     "jsx": "react-jsx"
23 |   },
24 |   "include": ["src"]
25 | }
26 | 


--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 |   - 'packages/server'
3 |   - 'packages/webapp'
4 | 


--------------------------------------------------------------------------------