11import React , { useState , useContext } from 'react'
22import getConfig from 'next/config'
3+ import { useRouter } from 'next/router'
34import moment from 'moment'
45import copy from 'copy-to-clipboard'
56import jwt from 'jsonwebtoken'
67import pluralize from 'pluralize'
8+ import { ThemeContext } from 'styled-components'
9+ import { transparentize } from 'polished'
710import { Copy } from '@styled-icons/boxicons-regular/Copy'
811import { Check } from '@styled-icons/boxicons-regular/Check'
912import { X } from '@styled-icons/boxicons-regular/X'
@@ -74,10 +77,13 @@ const Account = ({ token, invites = [], user, userRole }) => {
7477 const [ totpEnabled , setTotpEnabled ] = useState ( user . totp . enabled )
7578 const [ totpQrData , setTotpQrData ] = useState ( )
7679 const [ totpBackupCodes , setTotpBackupCodes ] = useState ( )
80+ const [ showDeleteAccountModal , setShowDeleteAccountModal ] = useState ( false )
7781
7882 const { addNotification } = useContext ( NotificationContext )
7983 const { setLoading } = useContext ( LoadingContext )
8084
85+ const theme = useContext ( ThemeContext )
86+
8187 const {
8288 publicRuntimeConfig : {
8389 SQ_API_URL ,
@@ -88,6 +94,8 @@ const Account = ({ token, invites = [], user, userRole }) => {
8894 } ,
8995 } = getConfig ( )
9096
97+ const router = useRouter ( )
98+
9199 const handleGenerateInvite = async ( e ) => {
92100 e . preventDefault ( )
93101 setLoading ( true )
@@ -283,6 +291,34 @@ const Account = ({ token, invites = [], user, userRole }) => {
283291 }
284292 }
285293
294+ const handleDeleteAccount = async ( e ) => {
295+ e . preventDefault ( )
296+ const form = new FormData ( e . target )
297+
298+ try {
299+ const deleteAccountRes = await fetch ( `${ SQ_API_URL } /account/delete` , {
300+ method : 'POST' ,
301+ headers : {
302+ 'Content-Type' : 'application/json' ,
303+ Authorization : `Bearer ${ token } ` ,
304+ } ,
305+ body : JSON . stringify ( {
306+ password : form . get ( 'password' ) ,
307+ } ) ,
308+ } )
309+
310+ if ( deleteAccountRes . status !== 200 ) {
311+ const reason = await deleteAccountRes . text ( )
312+ throw new Error ( reason )
313+ }
314+
315+ await router . push ( '/logout' )
316+ } catch ( e ) {
317+ addNotification ( 'error' , `Could not delete account: ${ e . message } ` )
318+ console . error ( e )
319+ }
320+ }
321+
286322 return (
287323 < >
288324 < SEO title = "My account" />
@@ -527,27 +563,46 @@ const Account = ({ token, invites = [], user, userRole }) => {
527563 ) }
528564 </ form >
529565 </ Box >
530- < Text as = "h2" mb = { 4 } >
531- Change password
532- </ Text >
533- < form onSubmit = { handleChangePassword } >
534- < Input
535- name = "password"
536- type = "password"
537- label = "Current password"
538- mb = { 4 }
539- required
540- />
541- < Input
542- name = "newPassword"
543- type = "password"
544- label = "New password"
545- autoComplete = "new-password"
546- mb = { 4 }
547- required
548- />
549- < Button > Change password</ Button >
550- </ form >
566+ < Box mb = { 5 } >
567+ < Text as = "h2" mb = { 4 } >
568+ Change password
569+ </ Text >
570+ < form onSubmit = { handleChangePassword } >
571+ < Input
572+ name = "password"
573+ type = "password"
574+ label = "Current password"
575+ mb = { 4 }
576+ required
577+ />
578+ < Input
579+ name = "newPassword"
580+ type = "password"
581+ label = "New password"
582+ autoComplete = "new-password"
583+ mb = { 4 }
584+ required
585+ />
586+ < Button > Change password</ Button >
587+ </ form >
588+ </ Box >
589+ { user . username !== 'admin' && (
590+ < Box
591+ bg = { transparentize ( 0.7 , theme . colors . error ) }
592+ borderRadius = { 1 }
593+ p = { 4 }
594+ >
595+ < Text as = "h2" mb = { 4 } >
596+ Danger zone
597+ </ Text >
598+ < Button
599+ variant = "danger"
600+ onClick = { ( ) => setShowDeleteAccountModal ( true ) }
601+ >
602+ Delete my account
603+ </ Button >
604+ </ Box >
605+ ) }
551606 { showInviteModal && (
552607 < Modal close = { ( ) => setShowInviteModal ( false ) } >
553608 < Text mb = { 5 } >
@@ -566,6 +621,7 @@ const Account = ({ token, invites = [], user, userRole }) => {
566621 < Box display = "flex" justifyContent = "flex-end" >
567622 < Button
568623 onClick = { ( ) => setShowInviteModal ( false ) }
624+ type = "button"
569625 variant = "secondary"
570626 mr = { 3 }
571627 >
@@ -576,6 +632,35 @@ const Account = ({ token, invites = [], user, userRole }) => {
576632 </ form >
577633 </ Modal >
578634 ) }
635+ { showDeleteAccountModal && (
636+ < Modal close = { ( ) => setShowDeleteAccountModal ( false ) } >
637+ < Text mb = { 5 } >
638+ Are you sure you want to delete your account? This action cannot be
639+ undone, and you may not be able to register again. Your personal
640+ information will be deleted but your uploaded torrents will remain.
641+ </ Text >
642+ < form onSubmit = { handleDeleteAccount } >
643+ < Input
644+ name = "password"
645+ type = "password"
646+ label = "Password"
647+ mb = { 4 }
648+ required
649+ />
650+ < Box display = "flex" justifyContent = "flex-end" >
651+ < Button
652+ onClick = { ( ) => setShowDeleteAccountModal ( false ) }
653+ type = "button"
654+ variant = "secondary"
655+ mr = { 3 }
656+ >
657+ Cancel
658+ </ Button >
659+ < Button variant = "danger" > Yes, delete my account</ Button >
660+ </ Box >
661+ </ form >
662+ </ Modal >
663+ ) }
579664 </ >
580665 )
581666}
0 commit comments