RemoveAccount.vue 7.5 KB

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