SignUp.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <template>
  2. <v-container
  3. class="fill-height"
  4. fluid
  5. >
  6. <v-row
  7. align="center"
  8. justify="center"
  9. >
  10. <v-col
  11. cols="12"
  12. sm="8"
  13. md="6"
  14. >
  15. <v-form @submit.prevent="signup" ref="form">
  16. <v-card class="elevation-12 pb-4">
  17. <v-toolbar
  18. color="primary"
  19. dark
  20. flat
  21. >
  22. <v-toolbar-title>Create new Account</v-toolbar-title>
  23. </v-toolbar>
  24. <v-card-text>
  25. <v-alert :value="!!(errors && errors.length)" type="error">
  26. <div v-if="errors.length > 1">
  27. <li v-for="error of errors" :key="error.message" >
  28. <b>{{ error.message }}</b>
  29. {{ error }}
  30. </li>
  31. </div>
  32. <div v-else>
  33. {{ errors[0] }}
  34. </div>
  35. </v-alert>
  36. <v-text-field
  37. v-model="email"
  38. label="Email"
  39. prepend-icon="mdi-email"
  40. outline
  41. required
  42. :disabled="working"
  43. :rules="email_rules"
  44. :error-messages="email_errors"
  45. @change="email_errors=[]"
  46. validate-on-blur
  47. ref="emailField"
  48. tabindex="1"
  49. />
  50. <p class="mt-4 pl-8 heading">
  51. To use our <strong>dynDNS service</strong>, enter a domain name here. After sign-up, we will send you
  52. instructions on how to configure your dynDNS client, such as you router.<br>
  53. If instead you are interested in <strong>general DNS hosting</strong>, please do not provide a domain
  54. name. After sign-up, you can login and create a token to use our DNS REST API.
  55. </p>
  56. <v-text-field
  57. v-model="domain"
  58. label="DynDNS domain (optional)"
  59. prepend-icon="mdi-dns"
  60. outline
  61. required
  62. :disabled="working"
  63. :rules="domain_rules"
  64. :error-messages="domain_errors"
  65. :suffix="'.' + LOCAL_PUBLIC_SUFFIXES[0]"
  66. @change="domain_errors=[]"
  67. class="lowercase"
  68. ref="domainField"
  69. tabindex="2"
  70. />
  71. <v-layout>
  72. <v-text-field
  73. v-model="captchaSolution"
  74. label="Type CAPTCHA text here"
  75. prepend-icon="mdi-account-check"
  76. outline
  77. required
  78. :disabled="working"
  79. :rules="captcha_rules"
  80. :error-messages="captcha_errors"
  81. @change="captcha_errors=[]"
  82. @keypress="captcha_errors=[]"
  83. class="uppercase"
  84. ref="captchaField"
  85. tabindex="3"
  86. />
  87. <div class="ml-4 text-center">
  88. <v-progress-circular
  89. indeterminate
  90. v-if="captchaWorking"
  91. ></v-progress-circular>
  92. <img
  93. v-if="captcha && !captchaWorking"
  94. :src="'data:image/png;base64,'+captcha.challenge"
  95. alt="Sign up is also possible by sending an email to our support."
  96. >
  97. <br/>
  98. <v-btn text outlined @click="getCaptcha(true)" :disabled="captchaWorking">New Captcha</v-btn>
  99. </div>
  100. </v-layout>
  101. <v-layout class="justify-center">
  102. <v-checkbox
  103. v-model="terms"
  104. type="checkbox"
  105. required
  106. :disabled="working"
  107. :rules="terms_rules"
  108. tabindex="4"
  109. >
  110. <template slot="label">
  111. <v-flex>
  112. Yes, I agree to the <a @click.stop="open_route('terms')">Terms of Use</a> and
  113. <a @click.stop="open_route('privacy-policy')">Privacy Policy</a>.
  114. </v-flex>
  115. </template>
  116. </v-checkbox>
  117. </v-layout>
  118. </v-card-text>
  119. <v-card-actions class="justify-center">
  120. <v-btn
  121. depressed
  122. class="px-4"
  123. color="primary"
  124. type="submit"
  125. :disabled="working"
  126. :loading="working"
  127. tabindex="5"
  128. >Sign up for Beta Account</v-btn>
  129. </v-card-actions>
  130. </v-card>
  131. </v-form>
  132. </v-col>
  133. </v-row>
  134. </v-container>
  135. </template>
  136. <script>
  137. import axios from 'axios';
  138. import {LOCAL_PUBLIC_SUFFIXES} from '../env';
  139. import {domain_pattern, email_pattern} from '../validation';
  140. const HTTP = axios.create({
  141. baseURL: '/api/v1/',
  142. headers: {
  143. },
  144. });
  145. export default {
  146. name: 'SignUp',
  147. data: () => ({
  148. valid: false,
  149. working: false,
  150. captchaWorking: true,
  151. errors: [],
  152. captcha: null,
  153. LOCAL_PUBLIC_SUFFIXES: LOCAL_PUBLIC_SUFFIXES,
  154. /* email field */
  155. email: '',
  156. email_rules: [v => !!email_pattern.test(v || '') || 'We need an email address for account recovery and technical support.'],
  157. email_errors: [],
  158. /* captcha field */
  159. captchaSolution: '',
  160. captcha_rules: [v => !!v || 'Please enter the text displayed in the picture so we are (somewhat) convinced you are human'],
  161. captcha_errors: [],
  162. /* terms field */
  163. terms: false,
  164. terms_rules: [v => !!v || 'You can only use our service if you agree with the terms'],
  165. /* domain field */
  166. domain: '',
  167. domain_rules: [v => !!domain_pattern.test(v + '.' + LOCAL_PUBLIC_SUFFIXES[0]) || 'Domain names can only contain letters, numbers, underscores (_), dots (.), and dashes (-), and must end in a letter.'],
  168. domain_errors: [],
  169. }),
  170. async mounted() {
  171. if ('email' in this.$route.params && this.$route.params.email !== undefined) {
  172. this.email = this.$route.params.email;
  173. }
  174. this.getCaptcha();
  175. this.initialFocus();
  176. },
  177. methods: {
  178. async open_route(route) {
  179. window.open(this.$router.resolve({name: route}).href);
  180. this.terms = !this.terms; // silly but easy fix for "accidentally" checking the box by clicking the link
  181. },
  182. async getCaptcha(focus = false) {
  183. this.captchaWorking = true;
  184. this.captchaSolution = "";
  185. try {
  186. this.captcha = (await HTTP.post('captcha/')).data;
  187. if(focus) {
  188. this.$refs.captchaField.focus()
  189. }
  190. } finally {
  191. this.captchaWorking = false;
  192. }
  193. },
  194. async initialFocus() {
  195. return this.email ? this.$refs.domainField.focus() : this.$refs.emailField.focus();
  196. },
  197. async signup() {
  198. if (!this.$refs.form.validate()) {
  199. return;
  200. }
  201. this.working = true;
  202. this.errors = [];
  203. let domain = this.domain === '' ? undefined : this.domain.toLowerCase() + '.' + this.LOCAL_PUBLIC_SUFFIXES[0];
  204. try {
  205. await HTTP.post('auth/', {
  206. email: this.email.toLowerCase(),
  207. password: null,
  208. captcha: {
  209. id: this.captcha.id,
  210. solution: this.captchaSolution.toUpperCase(),
  211. },
  212. domain: domain,
  213. });
  214. this.$router.push({name: 'welcome', params: domain !== '' ? {domain: domain} : {}});
  215. } catch (error) {
  216. if (error.response) {
  217. // status is not 2xx
  218. if (error.response.status < 500 && typeof error.response.data === 'object') {
  219. // 3xx or 4xx
  220. let extracted = false;
  221. this.getCaptcha(true);
  222. if ('captcha' in error.response.data) {
  223. if ('non_field_errors' in error.response.data.captcha) {
  224. this.captcha_errors = [error.response.data.captcha.non_field_errors[0]];
  225. extracted = true;
  226. }
  227. if ('solution' in error.response.data.captcha) {
  228. this.captcha_errors = error.response.data.captcha.solution;
  229. extracted = true;
  230. }
  231. }
  232. if ('domain' in error.response.data) {
  233. this.domain_errors = [error.response.data.domain[0]];
  234. extracted = true;
  235. }
  236. if ('email' in error.response.data) {
  237. this.email_errors = [error.response.data.email[0]];
  238. extracted = true;
  239. }
  240. if (!extracted) {
  241. this.errors = error.response;
  242. }
  243. } else {
  244. // 5xx
  245. this.errors = ['Something went wrong at the server, but we currently do not know why. The customer support was already notified.'];
  246. }
  247. } else if (error.request) {
  248. this.errors = ['Cannot contact our servers. Are you offline?'];
  249. } else {
  250. this.errors = [error.message];
  251. }
  252. }
  253. this.working = false;
  254. },
  255. },
  256. };
  257. </script>
  258. <style lang="scss">
  259. .uppercase input {
  260. text-transform: uppercase;
  261. }
  262. .lowercase input {
  263. text-transform: lowercase;
  264. }
  265. </style>