Settings.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <div>
  3. <metadata title="Settings" />
  4. <main-header />
  5. <div class="container">
  6. <!--Implement Validation-->
  7. <label class="label">Name</label>
  8. <div class="control is-grouped">
  9. <p class="control is-expanded has-icon has-icon-right">
  10. <input
  11. v-model="user.name"
  12. class="input"
  13. type="text"
  14. placeholder="Change name"
  15. />
  16. </p>
  17. <p class="control">
  18. <button class="button is-success" @click="changeName()">
  19. Save changes
  20. </button>
  21. </p>
  22. </div>
  23. <label class="label">Username</label>
  24. <div class="control is-grouped">
  25. <p class="control is-expanded has-icon has-icon-right">
  26. <input
  27. v-model="user.username"
  28. class="input"
  29. type="text"
  30. placeholder="Change username"
  31. />
  32. <!--Remove validation if it's their own without changing-->
  33. </p>
  34. <p class="control">
  35. <button class="button is-success" @click="changeUsername()">
  36. Save changes
  37. </button>
  38. </p>
  39. </div>
  40. <label class="label">Email</label>
  41. <div v-if="user.email" class="control is-grouped">
  42. <p class="control is-expanded has-icon has-icon-right">
  43. <input
  44. v-model="user.email.address"
  45. class="input"
  46. type="text"
  47. placeholder="Change email address"
  48. />
  49. <!--Remove validation if it's their own without changing-->
  50. </p>
  51. <p class="control is-expanded">
  52. <button class="button is-success" @click="changeEmail()">
  53. Save changes
  54. </button>
  55. </p>
  56. </div>
  57. <label v-if="password" class="label">Change Password</label>
  58. <div v-if="password" class="control is-grouped">
  59. <p class="control is-expanded has-icon has-icon-right">
  60. <input
  61. v-model="newPassword"
  62. class="input"
  63. type="password"
  64. placeholder="Change password"
  65. />
  66. </p>
  67. <p class="control is-expanded">
  68. <button class="button is-success" @click="changePassword()">
  69. Change password
  70. </button>
  71. </p>
  72. </div>
  73. <label class="label">Location</label>
  74. <div v-if="user" class="control is-grouped">
  75. <p class="control is-expanded has-icon has-icon-right">
  76. <input
  77. v-model="user.location"
  78. class="input"
  79. type="text"
  80. placeholder="Change location"
  81. />
  82. </p>
  83. <p class="control is-expanded">
  84. <button class="button is-success" @click="changeLocation()">
  85. Save changes
  86. </button>
  87. </p>
  88. </div>
  89. <label class="label">Bio</label>
  90. <div v-if="user" class="control is-grouped">
  91. <p class="control is-expanded has-icon has-icon-right">
  92. <textarea
  93. v-model="user.bio"
  94. class="textarea"
  95. type="text"
  96. placeholder="Change bio"
  97. />
  98. </p>
  99. <p class="control is-expanded">
  100. <button class="button is-success" @click="changeBio()">
  101. Save changes
  102. </button>
  103. </p>
  104. </div>
  105. <label v-if="password" class="label">Use nightmode</label>
  106. <div v-if="password" class="control is-grouped">
  107. <input
  108. :checked="nightmode"
  109. type="checkbox"
  110. @click="toggleNightmode()"
  111. />
  112. </div>
  113. <label v-if="!password" class="label">Add password</label>
  114. <div v-if="!password" class="control is-grouped">
  115. <button
  116. v-if="passwordStep === 1"
  117. class="button is-success"
  118. @click="requestPassword()"
  119. >
  120. Request password email
  121. </button>
  122. <br />
  123. <p
  124. v-if="passwordStep === 2"
  125. class="control is-expanded has-icon has-icon-right"
  126. >
  127. <input
  128. v-model="passwordCode"
  129. class="input"
  130. type="text"
  131. placeholder="Code"
  132. />
  133. </p>
  134. <p v-if="passwordStep === 2" class="control is-expanded">
  135. <button class="button is-success" v-on:click="verifyCode()">
  136. Verify code
  137. </button>
  138. </p>
  139. <p
  140. v-if="passwordStep === 3"
  141. class="control is-expanded has-icon has-icon-right"
  142. >
  143. <input
  144. v-model="setNewPassword"
  145. class="input"
  146. type="password"
  147. placeholder="New password"
  148. />
  149. </p>
  150. <p v-if="passwordStep === 3" class="control is-expanded">
  151. <button class="button is-success" @click="setPassword()">
  152. Set password
  153. </button>
  154. </p>
  155. </div>
  156. <a
  157. v-if="passwordStep === 1 && !password"
  158. href="#"
  159. @click="passwordStep = 2"
  160. >Skip this step</a
  161. >
  162. <a
  163. v-if="!github"
  164. class="button is-github"
  165. :href="`${serverDomain}/auth/github/link`"
  166. >
  167. <div class="icon">
  168. <img class="invert" src="/assets/social/github.svg" />
  169. </div>
  170. &nbsp; Link GitHub to account
  171. </a>
  172. <button
  173. v-if="password && github"
  174. class="button is-danger"
  175. @click="unlinkPassword()"
  176. >
  177. Remove logging in with password
  178. </button>
  179. <button
  180. v-if="password && github"
  181. class="button is-danger"
  182. @click="unlinkGitHub()"
  183. >
  184. Remove logging in with GitHub
  185. </button>
  186. <br />
  187. <button
  188. class="button is-warning"
  189. style="margin-top: 30px;"
  190. @click="removeSessions()"
  191. >
  192. Log out everywhere
  193. </button>
  194. </div>
  195. <main-footer />
  196. </div>
  197. </template>
  198. <script>
  199. import { mapState, mapActions } from "vuex";
  200. import Toast from "toasters";
  201. import MainHeader from "../MainHeader.vue";
  202. import MainFooter from "../MainFooter.vue";
  203. import io from "../../io";
  204. import validation from "../../validation";
  205. export default {
  206. components: { MainHeader, MainFooter },
  207. data() {
  208. return {
  209. user: {},
  210. newPassword: "",
  211. password: false,
  212. github: false,
  213. setNewPassword: "",
  214. passwordStep: 1,
  215. passwordCode: "",
  216. serverDomain: ""
  217. };
  218. },
  219. computed: mapState({
  220. userId: state => state.user.auth.userId,
  221. nightmode: state => state.user.preferences.nightmode
  222. }),
  223. mounted() {
  224. lofig.get("serverDomain").then(serverDomain => {
  225. this.serverDomain = serverDomain;
  226. });
  227. io.getSocket(socket => {
  228. this.socket = socket;
  229. this.socket.emit("users.findBySession", res => {
  230. if (res.status === "success") {
  231. this.user = res.data;
  232. this.password = this.user.password;
  233. this.github = this.user.github;
  234. } else {
  235. new Toast({
  236. content: "Your are currently not signed in",
  237. timeout: 3000
  238. });
  239. }
  240. });
  241. this.socket.on("event:user.linkPassword", () => {
  242. this.password = true;
  243. });
  244. this.socket.on("event:user.linkGitHub", () => {
  245. this.github = true;
  246. });
  247. this.socket.on("event:user.unlinkPassword", () => {
  248. this.password = false;
  249. });
  250. this.socket.on("event:user.unlinkGitHub", () => {
  251. this.github = false;
  252. });
  253. });
  254. },
  255. methods: {
  256. changeEmail() {
  257. const email = this.user.email.address;
  258. if (!validation.isLength(email, 3, 254))
  259. return new Toast({
  260. content: "Email must have between 3 and 254 characters.",
  261. timeout: 8000
  262. });
  263. if (
  264. email.indexOf("@") !== email.lastIndexOf("@") ||
  265. !validation.regex.emailSimple.test(email)
  266. )
  267. return new Toast({
  268. content: "Invalid email format.",
  269. timeout: 8000
  270. });
  271. return this.socket.emit(
  272. "users.updateEmail",
  273. this.userId,
  274. email,
  275. res => {
  276. if (res.status !== "success")
  277. new Toast({ content: res.message, timeout: 8000 });
  278. else
  279. new Toast({
  280. content: "Successfully changed email address",
  281. timeout: 4000
  282. });
  283. }
  284. );
  285. },
  286. changeUsername() {
  287. const { username } = this.user;
  288. if (!validation.isLength(username, 2, 32))
  289. return new Toast({
  290. content: "Username must have between 2 and 32 characters.",
  291. timeout: 8000
  292. });
  293. if (!validation.regex.azAZ09_.test(username))
  294. return new Toast({
  295. content:
  296. "Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.",
  297. timeout: 8000
  298. });
  299. return this.socket.emit(
  300. "users.updateUsername",
  301. this.userId,
  302. username,
  303. res => {
  304. if (res.status !== "success")
  305. new Toast({ content: res.message, timeout: 8000 });
  306. else
  307. new Toast({
  308. content: "Successfully changed username",
  309. timeout: 4000
  310. });
  311. }
  312. );
  313. },
  314. changeName() {
  315. const { name } = this.user;
  316. if (!validation.isLength(name, 1, 64))
  317. return new Toast({
  318. content: "Name must have between 1 and 64 characters.",
  319. timeout: 8000
  320. });
  321. return this.socket.emit(
  322. "users.updateName",
  323. this.userId,
  324. name,
  325. res => {
  326. if (res.status !== "success")
  327. new Toast({ content: res.message, timeout: 8000 });
  328. else
  329. new Toast({
  330. content: "Successfully changed name",
  331. timeout: 4000
  332. });
  333. }
  334. );
  335. },
  336. changeLocation() {
  337. const { location } = this.user;
  338. if (!validation.isLength(location, 0, 50))
  339. return new Toast({
  340. content: "Location must have between 0 and 50 characters.",
  341. timeout: 8000
  342. });
  343. return this.socket.emit(
  344. "users.updateLocation",
  345. this.userId,
  346. location,
  347. res => {
  348. if (res.status !== "success")
  349. new Toast({ content: res.message, timeout: 8000 });
  350. else
  351. new Toast({
  352. content: "Successfully changed location",
  353. timeout: 4000
  354. });
  355. }
  356. );
  357. },
  358. changeBio() {
  359. const { bio } = this.user;
  360. if (!validation.isLength(bio, 0, 200))
  361. return new Toast({
  362. content: "Bio must have between 0 and 200 characters.",
  363. timeout: 8000
  364. });
  365. return this.socket.emit(
  366. "users.updateBio",
  367. this.userId,
  368. bio,
  369. res => {
  370. if (res.status !== "success")
  371. new Toast({ content: res.message, timeout: 8000 });
  372. else
  373. new Toast({
  374. content: "Successfully changed bio",
  375. timeout: 4000
  376. });
  377. }
  378. );
  379. },
  380. changePassword() {
  381. const { newPassword } = this;
  382. if (!validation.isLength(newPassword, 6, 200))
  383. return new Toast({
  384. content: "Password must have between 6 and 200 characters.",
  385. timeout: 8000
  386. });
  387. if (!validation.regex.password.test(newPassword))
  388. return new Toast({
  389. content:
  390. "Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
  391. timeout: 8000
  392. });
  393. return this.socket.emit(
  394. "users.updatePassword",
  395. newPassword,
  396. res => {
  397. if (res.status !== "success")
  398. new Toast({ content: res.message, timeout: 8000 });
  399. else
  400. new Toast({
  401. content: "Successfully changed password",
  402. timeout: 4000
  403. });
  404. }
  405. );
  406. },
  407. requestPassword() {
  408. return this.socket.emit("users.requestPassword", res => {
  409. new Toast({ content: res.message, timeout: 8000 });
  410. if (res.status === "success") {
  411. this.passwordStep = 2;
  412. }
  413. });
  414. },
  415. verifyCode() {
  416. if (!this.passwordCode)
  417. return new Toast({
  418. content: "Code cannot be empty",
  419. timeout: 8000
  420. });
  421. return this.socket.emit(
  422. "users.verifyPasswordCode",
  423. this.passwordCode,
  424. res => {
  425. new Toast({ content: res.message, timeout: 8000 });
  426. if (res.status === "success") {
  427. this.passwordStep = 3;
  428. }
  429. }
  430. );
  431. },
  432. setPassword() {
  433. const newPassword = this.setNewPassword;
  434. if (!validation.isLength(newPassword, 6, 200))
  435. return new Toast({
  436. content: "Password must have between 6 and 200 characters.",
  437. timeout: 8000
  438. });
  439. if (!validation.regex.password.test(newPassword))
  440. return new Toast({
  441. content:
  442. "Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
  443. timeout: 8000
  444. });
  445. return this.socket.emit(
  446. "users.changePasswordWithCode",
  447. this.passwordCode,
  448. newPassword,
  449. res => {
  450. new Toast({ content: res.message, timeout: 8000 });
  451. }
  452. );
  453. },
  454. unlinkPassword() {
  455. this.socket.emit("users.unlinkPassword", res => {
  456. new Toast({ content: res.message, timeout: 8000 });
  457. });
  458. },
  459. unlinkGitHub() {
  460. this.socket.emit("users.unlinkGitHub", res => {
  461. new Toast({ content: res.message, timeout: 8000 });
  462. });
  463. },
  464. removeSessions() {
  465. this.socket.emit(`users.removeSessions`, this.userId, res => {
  466. new Toast({ content: res.message, timeout: 4000 });
  467. });
  468. },
  469. toggleNightmode() {
  470. localStorage.setItem("nightmode", !this.nightmode);
  471. this.changeNightmode(!this.nightmode);
  472. },
  473. ...mapActions("user/preferences", ["changeNightmode"])
  474. }
  475. };
  476. </script>
  477. <style lang="scss" scoped>
  478. @import "styles/global.scss";
  479. .night-mode {
  480. .label {
  481. color: #ddd;
  482. }
  483. }
  484. .container {
  485. padding: 25px;
  486. }
  487. a {
  488. color: $primary-color !important;
  489. }
  490. </style>