RemoveAccount.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, onMounted } from "vue";
  3. import { useRoute } from "vue-router";
  4. import Toast from "toasters";
  5. import { storeToRefs } from "pinia";
  6. import { useSettingsStore } from "@/stores/settings";
  7. import { useWebsocketsStore } from "@/stores/websockets";
  8. import { useRemoveAccountStore } from "@/stores/removeAccount";
  9. import { useModalsStore } from "@/stores/modals";
  10. const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
  11. const QuickConfirm = defineAsyncComponent(
  12. () => import("@/components/QuickConfirm.vue")
  13. );
  14. const props = defineProps({
  15. modalUuid: { type: String, required: true }
  16. });
  17. const settingsStore = useSettingsStore();
  18. const route = useRoute();
  19. const { socket } = useWebsocketsStore();
  20. const removeAccountStore = useRemoveAccountStore(props);
  21. const { githubLinkConfirmed } = storeToRefs(removeAccountStore);
  22. const { isPasswordLinked, isGithubLinked } = settingsStore;
  23. const { closeCurrentModal } = useModalsStore();
  24. const step = ref("confirm-identity");
  25. const apiDomain = ref("");
  26. const accountRemovalMessage = ref("");
  27. const password = ref({
  28. value: "",
  29. visible: false
  30. });
  31. const passwordElement = ref();
  32. const githubAuthentication = ref(false);
  33. const checkForAutofill = (cb, event) => {
  34. if (
  35. event.target.value !== "" &&
  36. event.inputType === undefined &&
  37. event.data === undefined &&
  38. event.dataTransfer === undefined &&
  39. event.isComposing === undefined
  40. )
  41. cb();
  42. };
  43. const submitOnEnter = (cb, event) => {
  44. if (event.which === 13) cb();
  45. };
  46. const togglePasswordVisibility = () => {
  47. if (passwordElement.value.type === "password") {
  48. passwordElement.value.type = "text";
  49. password.value.visible = true;
  50. } else {
  51. passwordElement.value.type = "password";
  52. password.value.visible = false;
  53. }
  54. };
  55. const confirmPasswordMatch = () =>
  56. socket.dispatch("users.confirmPasswordMatch", password.value.value, res => {
  57. if (res.status === "success") step.value = "remove-account";
  58. else new Toast(res.message);
  59. });
  60. const confirmGithubLink = () =>
  61. socket.dispatch("users.confirmGithubLink", res => {
  62. if (res.status === "success") {
  63. if (res.data.linked) step.value = "remove-account";
  64. else {
  65. new Toast(
  66. `Your GitHub account isn't linked. Please re-link your account and try again.`
  67. );
  68. step.value = "relink-github";
  69. }
  70. } else new Toast(res.message);
  71. });
  72. const relinkGithub = () => {
  73. localStorage.setItem(
  74. "github_redirect",
  75. `${window.location.pathname + window.location.search}${
  76. !route.query.removeAccount ? "&removeAccount=relinked-github" : ""
  77. }`
  78. );
  79. };
  80. const remove = () =>
  81. socket.dispatch("users.remove", res => {
  82. if (res.status === "success") {
  83. return socket.dispatch("users.logout", () =>
  84. lofig.get("cookie").then(cookie => {
  85. document.cookie = `${cookie.SIDname}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
  86. closeCurrentModal();
  87. window.location.href = "/";
  88. })
  89. );
  90. }
  91. return new Toast(res.message);
  92. });
  93. onMounted(async () => {
  94. apiDomain.value = await lofig.get("backend.apiDomain");
  95. accountRemovalMessage.value = await lofig.get("messages.accountRemoval");
  96. githubAuthentication.value = await lofig.get(
  97. "siteSettings.githubAuthentication"
  98. );
  99. if (githubLinkConfirmed.value === true) confirmGithubLink();
  100. });
  101. </script>
  102. <template>
  103. <modal
  104. title="Confirm Account Removal"
  105. class="confirm-account-removal-modal"
  106. >
  107. <template #body>
  108. <div id="steps">
  109. <p
  110. class="step"
  111. :class="{ selected: step === 'confirm-identity' }"
  112. >
  113. 1
  114. </p>
  115. <span class="divider"></span>
  116. <p
  117. class="step"
  118. :class="{
  119. selected:
  120. (isPasswordLinked && step === 'export-data') ||
  121. step === 'relink-github'
  122. }"
  123. >
  124. 2
  125. </p>
  126. <span class="divider"></span>
  127. <p
  128. class="step"
  129. :class="{
  130. selected:
  131. (isPasswordLinked && step === 'remove-account') ||
  132. step === 'export-data'
  133. }"
  134. >
  135. 3
  136. </p>
  137. <span class="divider" v-if="!isPasswordLinked"></span>
  138. <p
  139. class="step"
  140. :class="{ selected: step === 'remove-account' }"
  141. v-if="!isPasswordLinked"
  142. >
  143. 4
  144. </p>
  145. </div>
  146. <div
  147. class="content-box"
  148. id="password-linked"
  149. v-if="
  150. step === 'confirm-identity' &&
  151. (isPasswordLinked || !githubAuthentication)
  152. "
  153. >
  154. <h2 class="content-box-title">Enter your password</h2>
  155. <p class="content-box-description">
  156. Confirming your password will let us verify your identity.
  157. </p>
  158. <p class="content-box-optional-helper">
  159. <router-link id="forgot-password" to="/reset_password">
  160. Forgot password?
  161. </router-link>
  162. </p>
  163. <div class="content-box-inputs">
  164. <div class="control is-grouped input-with-button">
  165. <div id="password-visibility-container">
  166. <input
  167. class="input"
  168. type="password"
  169. placeholder="Enter password here..."
  170. autofocus
  171. ref="passwordElement"
  172. v-model="password.value"
  173. @input="
  174. checkForAutofill(
  175. confirmPasswordMatch,
  176. $event
  177. )
  178. "
  179. @keypress="
  180. submitOnEnter(confirmPasswordMatch, $event)
  181. "
  182. />
  183. <a @click="togglePasswordVisibility()">
  184. <i class="material-icons">
  185. {{
  186. !password.visible
  187. ? "visibility"
  188. : "visibility_off"
  189. }}
  190. </i>
  191. </a>
  192. </div>
  193. <p class="control">
  194. <button
  195. class="button is-info"
  196. @click="confirmPasswordMatch()"
  197. >
  198. Check
  199. </button>
  200. </p>
  201. </div>
  202. </div>
  203. </div>
  204. <div
  205. class="content-box"
  206. v-else-if="
  207. githubAuthentication &&
  208. isGithubLinked &&
  209. step === 'confirm-identity'
  210. "
  211. >
  212. <h2 class="content-box-title">Verify your GitHub</h2>
  213. <p class="content-box-description">
  214. Check your account is still linked to remove your account.
  215. </p>
  216. <div class="content-box-inputs">
  217. <a class="button is-github" @click="confirmGithubLink()">
  218. <div class="icon">
  219. <img
  220. class="invert"
  221. src="/assets/social/github.svg"
  222. />
  223. </div>
  224. &nbsp; Check GitHub is linked
  225. </a>
  226. </div>
  227. </div>
  228. <div
  229. class="content-box"
  230. v-if="githubAuthentication && step === 'relink-github'"
  231. >
  232. <h2 class="content-box-title">Re-link GitHub</h2>
  233. <p class="content-box-description">
  234. Re-link your GitHub account in order to verify your
  235. identity.
  236. </p>
  237. <div class="content-box-inputs">
  238. <a
  239. class="button is-github"
  240. @click="relinkGithub()"
  241. :href="`${apiDomain}/auth/github/link`"
  242. >
  243. <div class="icon">
  244. <img
  245. class="invert"
  246. src="/assets/social/github.svg"
  247. />
  248. </div>
  249. &nbsp; Re-link GitHub to account
  250. </a>
  251. </div>
  252. </div>
  253. <div v-if="step === 'export-data'">
  254. DOWNLOAD A BACKUP OF YOUR DATA BEFORE ITS PERMENATNELY DELETED
  255. </div>
  256. <div
  257. class="content-box"
  258. id="remove-account-container"
  259. v-if="step === 'remove-account'"
  260. >
  261. <h2 class="content-box-title">Remove your account</h2>
  262. <p class="content-box-description">
  263. {{ accountRemovalMessage }}
  264. </p>
  265. <div class="content-box-inputs">
  266. <quick-confirm placement="right" @confirm="remove()">
  267. <button class="button">
  268. <i class="material-icons">delete</i>
  269. &nbsp;Remove Account
  270. </button>
  271. </quick-confirm>
  272. </div>
  273. </div>
  274. </template>
  275. </modal>
  276. </template>
  277. <style lang="less">
  278. .confirm-account-removal-modal {
  279. .modal-card {
  280. width: 650px;
  281. }
  282. }
  283. </style>
  284. <style lang="less" scoped>
  285. h2 {
  286. margin: 0;
  287. }
  288. .content-box {
  289. margin-top: 20px;
  290. max-width: unset;
  291. }
  292. #steps {
  293. margin-top: 0;
  294. }
  295. #password-linked {
  296. #password-visibility-container {
  297. width: 100%;
  298. }
  299. > a {
  300. color: var(--primary-color);
  301. }
  302. }
  303. .control {
  304. margin-bottom: 0 !important;
  305. }
  306. #remove-account-container .content-box-inputs {
  307. width: fit-content;
  308. }
  309. </style>