1
0

Register.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, onMounted } from "vue";
  3. import Toast from "toasters";
  4. import { storeToRefs } from "pinia";
  5. import { useConfigStore } from "@/stores/config";
  6. import { useUserAuthStore } from "@/stores/userAuth";
  7. import { useModalsStore } from "@/stores/modals";
  8. import validation from "@/validation";
  9. import { useEvents } from "@/composables/useEvents";
  10. import { useWebsocketStore } from "@/stores/websocket";
  11. import { useForm } from "@/composables/useForm";
  12. const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
  13. const InputHelpBox = defineAsyncComponent(
  14. () => import("@/components/InputHelpBox.vue")
  15. );
  16. const passwordVisible = ref(false);
  17. const passwordElement = ref();
  18. const configStore = useConfigStore();
  19. const { registrationDisabled } = storeToRefs(configStore);
  20. const { openModal, closeCurrentModal } = useModalsStore();
  21. const { runJob } = useWebsocketStore();
  22. const { onReady } = useEvents();
  23. const { login } = useUserAuthStore();
  24. const togglePasswordVisibility = () => {
  25. if (passwordElement.value.type === "password") {
  26. passwordElement.value.type = "text";
  27. passwordVisible.value = true;
  28. } else {
  29. passwordElement.value.type = "password";
  30. passwordVisible.value = false;
  31. }
  32. };
  33. const changeToLoginModal = () => {
  34. closeCurrentModal();
  35. openModal("login");
  36. };
  37. const { inputs, validate, save } = useForm(
  38. {
  39. username: {
  40. value: null,
  41. validate: (value: string) => {
  42. if (!validation.isLength(value, 2, 32))
  43. return "Username must have between 2 and 32 characters.";
  44. if (!validation.regex.azAZ09_.test(value))
  45. return "Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.";
  46. if (value.replaceAll(/[_]/g, "").length === 0)
  47. return "Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _, and there has to be at least one letter or number.";
  48. return true;
  49. }
  50. },
  51. emailAddress: {
  52. value: null,
  53. validate: (value: string) => {
  54. if (!validation.isLength(value, 3, 254))
  55. return "Email address must have between 3 and 254 characters.";
  56. if (
  57. value.indexOf("@") !== value.lastIndexOf("@") ||
  58. !validation.regex.emailSimple.test(value)
  59. )
  60. return "Invalid email address format.";
  61. return true;
  62. }
  63. },
  64. password: {
  65. value: null,
  66. validate: (value: string) => {
  67. if (!validation.isLength(value, 6, 200))
  68. return "Password must have between 6 and 200 characters.";
  69. if (!validation.regex.password.test(value))
  70. return "Include at least one lowercase letter, one uppercase letter, one number and one special character.";
  71. return true;
  72. }
  73. }
  74. },
  75. ({ status, messages, values }, resolve, reject) => {
  76. if (status === "success") {
  77. runJob('data.users.register', {
  78. query: values
  79. })
  80. .then(async data => {
  81. await login(data.sessionId);
  82. window.location.reload();
  83. resolve();
  84. })
  85. .catch(reject);
  86. } else {
  87. if (status === "unchanged") new Toast(messages.unchanged);
  88. else if (status === "error")
  89. Object.values(messages).forEach(message => {
  90. new Toast({ content: message, timeout: 8000 });
  91. });
  92. resolve();
  93. }
  94. }
  95. );
  96. onMounted(async () => {
  97. await onReady(async () => {
  98. if (registrationDisabled.value) {
  99. new Toast("Registration is disabled.");
  100. closeCurrentModal();
  101. return;
  102. }
  103. });
  104. });
  105. </script>
  106. <template>
  107. <div>
  108. <modal
  109. title="Register"
  110. class="register-modal"
  111. :size="'slim'"
  112. @closed="closeCurrentModal()"
  113. >
  114. <template #body>
  115. <form>
  116. <!-- email address -->
  117. <p class="control">
  118. <label class="label">Email</label>
  119. <input
  120. v-model="inputs.emailAddress.value"
  121. class="input"
  122. type="email"
  123. autocomplete="email"
  124. placeholder="Email..."
  125. @input="validate('emailAddress')"
  126. @keyup.enter="save()"
  127. autofocus
  128. />
  129. </p>
  130. <transition name="fadein-helpbox">
  131. <input-help-box
  132. :entered="inputs.emailAddress.value?.length > 1"
  133. :valid="inputs.emailAddress.errors.length === 0"
  134. :message="
  135. inputs.emailAddress.errors[0] ?? 'Everything looks great!'
  136. "
  137. />
  138. </transition>
  139. <!-- username -->
  140. <p class="control">
  141. <label class="label">Username</label>
  142. <input
  143. v-model="inputs.username.value"
  144. class="input"
  145. type="text"
  146. autocomplete="username"
  147. placeholder="Username..."
  148. @input="validate('username')"
  149. @keyup.enter="save()"
  150. />
  151. </p>
  152. <transition name="fadein-helpbox">
  153. <input-help-box
  154. :entered="inputs.username.value?.length > 1"
  155. :valid="inputs.username.errors.length === 0"
  156. :message="
  157. inputs.username.errors[0] ?? 'Everything looks great!'
  158. "
  159. />
  160. </transition>
  161. <!-- password -->
  162. <p class="control">
  163. <label class="label">Password</label>
  164. </p>
  165. <div id="password-visibility-container">
  166. <input
  167. v-model="inputs.password.value"
  168. class="input"
  169. type="password"
  170. autocomplete="new-password"
  171. ref="passwordElement"
  172. placeholder="Password..."
  173. @input="validate('password')"
  174. @keyup.enter="save()"
  175. />
  176. <a @click="togglePasswordVisibility()">
  177. <i class="material-icons">
  178. {{
  179. !passwordVisible
  180. ? "visibility"
  181. : "visibility_off"
  182. }}
  183. </i>
  184. </a>
  185. </div>
  186. <transition name="fadein-helpbox">
  187. <input-help-box
  188. :entered="inputs.password.value?.length > 1"
  189. :valid="inputs.password.errors.length === 0"
  190. :message="
  191. inputs.password.errors[0] ?? 'Everything looks great!'
  192. "
  193. />
  194. </transition>
  195. <br />
  196. <p>
  197. By registering you agree to our
  198. <router-link to="/terms" @click="closeCurrentModal()">
  199. Terms of Service
  200. </router-link>
  201. and
  202. <router-link to="/privacy" @click="closeCurrentModal()">
  203. Privacy Policy</router-link
  204. >.
  205. </p>
  206. </form>
  207. </template>
  208. <template #footer>
  209. <div id="actions">
  210. <button class="button is-primary" @click="save()">
  211. Register
  212. </button>
  213. </div>
  214. <p class="content-box-optional-helper">
  215. <a @click="changeToLoginModal()">
  216. Already have an account?
  217. </a>
  218. </p>
  219. </template>
  220. </modal>
  221. </div>
  222. </template>
  223. <style lang="less" scoped>
  224. .night-mode {
  225. .modal-card,
  226. .modal-card-head,
  227. .modal-card-body,
  228. .modal-card-foot {
  229. background-color: var(--dark-grey-3);
  230. }
  231. .label,
  232. p:not(.help) {
  233. color: var(--light-grey-2);
  234. }
  235. }
  236. .control {
  237. margin-bottom: 2px !important;
  238. }
  239. .modal-card-foot {
  240. display: flex;
  241. justify-content: space-between;
  242. flex-wrap: wrap;
  243. .content-box-optional-helper {
  244. margin-top: 0;
  245. }
  246. }
  247. .invert {
  248. filter: brightness(5);
  249. }
  250. a {
  251. color: var(--primary-color);
  252. }
  253. </style>