Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

Commit

Permalink
Merge pull request #358 from Swetrix/improvement/account-deletion
Browse files Browse the repository at this point in the history
(improvement) Account deletion feedback
  • Loading branch information
Blaumaus authored Sep 8, 2023
2 parents a7d3e2f + 12f93c0 commit eaf3a34
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 11 deletions.
8 changes: 6 additions & 2 deletions app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,13 @@ export const signup = (data: {
throw new Error(errorsArray)
})

export const deleteUser = () =>
export const deleteUser = (deletionFeedback?: string) =>
api
.delete('/user')
.delete('/user', {
data: {
feedback: deletionFeedback,
},
})
.then((response): {} => response.data)
.catch((error) => {
throw new Error(JSON.stringify(error.response.data))
Expand Down
2 changes: 2 additions & 0 deletions app/pages/UserSettings/UserSettings.container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const mapDispatchToProps = (dispatch: AppDispatch) => ({
},
onDelete: (
t: (key: string) => string,
deletionFeedback: string,
onSuccess: {
(): void;
},
Expand All @@ -46,6 +47,7 @@ const mapDispatchToProps = (dispatch: AppDispatch) => ({
trackCustom('ACCOUNT_DELETED')
onSuccess()
},
deletionFeedback,
t,
),
)
Expand Down
25 changes: 21 additions & 4 deletions app/pages/UserSettings/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import Select from 'ui/Select'
import Checkbox from 'ui/Checkbox'
import PaidFeature from 'modals/PaidFeature'
import TimezonePicker from 'ui/TimezonePicker'
import Textarea from 'ui/Textarea'
import Loader from 'ui/Loader'
import {
isValidEmail,
Expand Down Expand Up @@ -72,7 +73,7 @@ dayjs.extend(utc)
const timeFormatArray = _map(TimeFormat, (key) => key)

interface IProps {
onDelete: (t: (key: string) => string, callback: () => void) => void,
onDelete: (t: (key: string) => string, deletionFeedback: string, callback: () => void) => void,
onDeleteProjectCache: () => void,
removeProject: (id: string) => void,
removeShareProject: (id: string) => void,
Expand Down Expand Up @@ -152,6 +153,7 @@ const UserSettings = ({
const translatedFrequencies: string[] = _map(reportFrequencies, (key) => t(`profileSettings.${key}`)) // useMemo(_map(reportFrequencies, (key) => t(`profileSettings.${key}`)), [t])
const translatedTimeFormat: string[] = _map(TimeFormat, (key) => t(`profileSettings.${key}`)) // useMemo(_map(TimeFormat, (key) => t(`profileSettings.${key}`)), [t])
const [settingUpdating, setSettingUpdating] = useState<boolean>(false)
const [deletionFeedback, setDeletionFeedback] = useState<string>('')

const copyTimerRef = useRef(null)

Expand Down Expand Up @@ -360,7 +362,7 @@ const UserSettings = ({
}

const _onDelete = () => {
onDelete(t, () => {
onDelete(t, deletionFeedback, () => {
navigate(routes.main)
})
}
Expand Down Expand Up @@ -867,7 +869,10 @@ const UserSettings = ({
isOpened={showExportModal}
/>
<Modal
onClose={() => setShowModal(false)}
onClose={() => {
setDeletionFeedback('')
setShowModal(false)
}}
onSubmit={() => {
setShowModal(false)
_onDelete()
Expand All @@ -877,7 +882,19 @@ const UserSettings = ({
title={t('profileSettings.qDelete')}
submitType='danger'
type='error'
message={t('profileSettings.deactivateConfirmation')}
message={(
<>
{t('profileSettings.deactivateConfirmation')}
<Textarea
id='feedback'
className='mt-4'
placeholder={t('profileSettings.deletionFeedback')}
onChange={(e) => setDeletionFeedback(e.target.value)}
value={deletionFeedback}
label={t('profileSettings.deletionFeedbackLabel')}
/>
</>
)}
isOpened={showModal}
/>
<Modal
Expand Down
4 changes: 2 additions & 2 deletions app/redux/sagas/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ const updateUserProfileAsync = (data: Partial<IUser>, callback = (item: any) =>
payload: { data, callback },
})

const deleteAccountAsync = (errorCallback?: (e: string) => {}, successCallback?: (str?: string) => void, t?: (str: string) => {}) => {
const deleteAccountAsync = (errorCallback?: (e: string) => {}, successCallback?: (str?: string) => void, deletionFeedback?: string, t?: (str: string) => {}) => {
return {
type: types.DELETE_ACCOUNT_ASYNC,
payload: {
errorCallback, successCallback, t,
errorCallback, successCallback, t, deletionFeedback,
},
}
}
Expand Down
5 changes: 3 additions & 2 deletions app/redux/sagas/auth/workers/deleteUserAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { authActions } from 'redux/reducers/auth'
import { alertsActions } from 'redux/reducers/alerts'
const { deleteUser } = require('api')

export default function* deleteUserAccountWorker({ payload: { errorCallback, successCallback, t } }: {
export default function* deleteUserAccountWorker({ payload: { errorCallback, successCallback, t, deletionFeedback } }: {
payload: {
errorCallback: (error: any) => void,
successCallback: () => void,
t: (key: string) => string,
deletionFeedback: string,
},
}) {
try {
yield call(deleteUser)
yield call(deleteUser, deletionFeedback)
yield call(successCallback)
yield put(authActions.deleteAccountSuccess())
yield put(alertsActions.accountDeleted({
Expand Down
2 changes: 1 addition & 1 deletion app/ui/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const Modal = ({
)}
<div className='mt-3 text-center sm:mt-0 sm:text-left w-full'>
{title && (
<Dialog.Title as='h3' className='flex items-center text-lg leading-6 font-medium text-gray-900 dark:text-gray-50'>
<Dialog.Title as='h3' className='flex items-center justify-center sm:justify-start text-lg leading-6 font-medium text-gray-900 dark:text-gray-50'>
{title}
{isBeta && (
<Beta className='ml-10' />
Expand Down
102 changes: 102 additions & 0 deletions app/ui/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { memo } from 'react'
import cx from 'clsx'
import _isEmpty from 'lodash/isEmpty'
import PropTypes from 'prop-types'
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'
import Beta from 'ui/Beta'

interface ITextarea {
value: string | number,
label?: string,
hint?: string,
placeholder?: string,
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void,
id?: string,
type?: string,
className?: string,
error?: string | boolean,
name?: string,
disabled?: boolean,
isBeta?: boolean,
}

const Textarea = ({
label, hint, placeholder, type, id, name, className, onChange, error, value, disabled, isBeta,
}: ITextarea) => {
const identifier = `textarea-${id || name || type}`
const isError = !_isEmpty(error)

return (
<div className={className}>
{label && (
<label htmlFor={identifier} className='flex text-sm font-medium text-gray-700 dark:text-gray-200'>
{label}
{isBeta && (
<div className='ml-5'>
<Beta />
</div>
)}
</label>
)}
<div className='mt-1 relative'>
<textarea
rows={4}
name={identifier}
id={identifier}
className={cx('shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:text-gray-50 dark:placeholder-gray-400 dark:border-slate-800/25 dark:bg-slate-800 rounded-md', {
'border-red-300 text-red-900 placeholder-red-300': isError,
'cursor-text': disabled,
})}
value={value}
onChange={onChange}
placeholder={placeholder}
disabled={disabled}
/>
{isError && (
<div className='absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none'>
<ExclamationCircleIcon className='h-5 w-5 text-red-500' aria-hidden />
</div>
)}
</div>
<p className='mt-2 text-sm text-gray-500 dark:text-gray-300 whitespace-pre-line' id={`${identifier}-optional`}>{hint}</p>
{isError && (
<p className='mt-2 text-sm text-red-600 dark:text-red-500' id='email-error'>{error}</p>
)}
</div>
)
}

Textarea.propTypes = {
value: PropTypes.oneOfType([
PropTypes.string, PropTypes.number,
]).isRequired,
label: PropTypes.string,
hint: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
id: PropTypes.string,
type: PropTypes.string,
className: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.string, PropTypes.bool,
]),
name: PropTypes.string,
disabled: PropTypes.bool,
isBeta: PropTypes.bool,
}

Textarea.defaultProps = {
label: '',
hint: '',
placeholder: '',
onChange: () => { },
id: '',
type: '',
className: '',
error: null,
name: '',
disabled: false,
isBeta: false,
}

export default memo(Textarea)
2 changes: 2 additions & 0 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@
"requestExport": "Request data export",
"delete": "Delete account",
"deactivateConfirmation": "Are you sure you want to deactivate your account?\nAll of your data will be permanently removed from our servers forever.\nThis action cannot be undone.",
"deletionFeedbackLabel": "Why are you leaving? (optional)",
"deletionFeedback": "We're sad to see you go, could you please tell us why you're leaving so we could improve our service?",
"exportConfirmation": "As requested by Art. 20 of General Data Protection Regulation (GDPR) the you have the right to receive your personal data that we store. This report does not include events data per project.\nThe data report will be sent to your email address.\nNote: you can request the data export only once per two weeks.",
"dataExport": "Data export",
"aDelete": "Delete my account",
Expand Down

0 comments on commit eaf3a34

Please sign in to comment.