asyncForm.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import App from '../base'
  2. App.directive('asyncForm', [
  3. '$http',
  4. 'validateCaptcha',
  5. 'validateCaptchaV3',
  6. function ($http, validateCaptcha, validateCaptchaV3) {
  7. return {
  8. controller: [
  9. '$scope',
  10. '$location',
  11. function ($scope, $location) {
  12. this.getEmail = () => $scope.email
  13. this.getEmailFromQuery = () =>
  14. $location.search().email || $location.search().new_email
  15. return this
  16. },
  17. ],
  18. link(scope, element, attrs, ctrl) {
  19. let response
  20. const formName = attrs.asyncForm
  21. scope[attrs.name].response = response = {}
  22. scope[attrs.name].inflight = false
  23. scope.email =
  24. scope.email ||
  25. scope.usersEmail ||
  26. ctrl.getEmailFromQuery() ||
  27. attrs.newEmail
  28. const validateCaptchaIfEnabled = function (callback) {
  29. scope.$applyAsync(() => {
  30. scope[attrs.name].inflight = true
  31. })
  32. if (attrs.captchaActionName) {
  33. validateCaptchaV3(attrs.captchaActionName)
  34. }
  35. if (attrs.captcha != null) {
  36. validateCaptcha(callback)
  37. } else {
  38. callback()
  39. }
  40. }
  41. const _submitRequest = function (grecaptchaResponse) {
  42. const formData = {}
  43. for (const data of Array.from(element.serializeArray())) {
  44. formData[data.name] = data.value
  45. }
  46. if (grecaptchaResponse) {
  47. formData['g-recaptcha-response'] = grecaptchaResponse
  48. }
  49. // clear the response object which may be referenced downstream
  50. Object.keys(response).forEach(field => delete response[field])
  51. // for asyncForm prevent automatic redirect to /login if
  52. // authentication fails, we will handle it ourselves
  53. const httpRequestFn = _httpRequestFn(element.attr('method'))
  54. return httpRequestFn(element.attr('action'), formData, {
  55. disableAutoLoginRedirect: true,
  56. })
  57. .then(function (httpResponse) {
  58. const { data, headers } = httpResponse
  59. scope[attrs.name].inflight = false
  60. response.success = true
  61. response.error = false
  62. const onSuccessHandler = scope[attrs.onSuccess]
  63. if (onSuccessHandler) {
  64. onSuccessHandler(httpResponse)
  65. return
  66. }
  67. if (data.redir) {
  68. ga('send', 'event', formName, 'success')
  69. return (window.location = data.redir)
  70. } else if (data.message) {
  71. response.message = data.message
  72. if (data.message.type === 'error') {
  73. response.success = false
  74. response.error = true
  75. return ga('send', 'event', formName, 'failure', data.message)
  76. } else {
  77. return ga('send', 'event', formName, 'success')
  78. }
  79. } else if (scope.$eval(attrs.asyncFormDownloadResponse)) {
  80. const blob = new Blob([data], {
  81. type: headers('Content-Type'),
  82. })
  83. location.href = URL.createObjectURL(blob) // Trigger file save
  84. }
  85. })
  86. .catch(function (httpResponse) {
  87. const { data, status } = httpResponse
  88. scope[attrs.name].inflight = false
  89. response.success = false
  90. response.error = true
  91. response.status = status
  92. response.data = data
  93. const onErrorHandler = scope[attrs.onError]
  94. if (onErrorHandler) {
  95. onErrorHandler(httpResponse)
  96. return
  97. }
  98. let responseMessage
  99. if (data.message && data.message.text) {
  100. responseMessage = data.message.text
  101. } else {
  102. responseMessage = data.message
  103. }
  104. if (status === 400) {
  105. // Bad Request
  106. response.message = {
  107. text:
  108. responseMessage ||
  109. 'Invalid Request. Please correct the data and try again.',
  110. type: 'error',
  111. }
  112. } else if (status === 403) {
  113. // Forbidden
  114. response.message = {
  115. text:
  116. responseMessage ||
  117. 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.',
  118. type: 'error',
  119. }
  120. } else if (status === 429) {
  121. response.message = {
  122. text:
  123. responseMessage ||
  124. 'Too many attempts. Please wait for a while and try again.',
  125. type: 'error',
  126. }
  127. } else {
  128. response.message = {
  129. text:
  130. responseMessage ||
  131. 'Something went wrong talking to the server :(. Please try again.',
  132. type: 'error',
  133. }
  134. }
  135. ga('send', 'event', formName, 'failure', data.message)
  136. })
  137. }
  138. const submit = () =>
  139. validateCaptchaIfEnabled(response => _submitRequest(response))
  140. const _httpRequestFn = (method = 'post') => {
  141. const $HTTP_FNS = {
  142. post: $http.post,
  143. get: $http.get,
  144. }
  145. return $HTTP_FNS[method.toLowerCase()]
  146. }
  147. element.on('submit', function (e) {
  148. e.preventDefault()
  149. submit()
  150. })
  151. if (attrs.autoSubmit) {
  152. submit()
  153. }
  154. },
  155. }
  156. },
  157. ])
  158. App.directive('formMessages', function () {
  159. return {
  160. restrict: 'E',
  161. template: `\
  162. <div class="alert" ng-class="{
  163. 'alert-danger': form.response.message.type == 'error',
  164. 'alert-success': form.response.message.type != 'error'
  165. }" ng-show="!!form.response.message" ng-bind-html="form.response.message.text">
  166. </div>
  167. <div ng-transclude></div>\
  168. `,
  169. transclude: true,
  170. scope: {
  171. form: '=for',
  172. },
  173. }
  174. })