Skip to content

Commit

Permalink
Delete Account and Data (#931)
Browse files Browse the repository at this point in the history
https://github.com/user-attachments/assets/0e8e2779-3656-4be1-9fd3-6da4028ad2dd


<!-- This is an auto-generated comment: release notes by OSS
Entelligence.AI -->
### Summary by Entelligence.AI

- New Feature: Introduced a new `DeleteAccount` feature in the settings,
allowing users to delete their account and all associated data.
- Refactor: Updated the method of displaying charging articles in device
settings for better user experience.
- Enhancement: Improved dialog functionality by adding customizable
cancel button text and action.
- New Feature: Backend now supports comprehensive user data deletion,
ensuring complete removal of user data upon account deletion.
- Error Handling: Enhanced exception handling during the account
deletion process to ensure smooth user experience and proper logging.
<!-- end of auto-generated comment: release notes by OSS Entelligence.AI
-->
  • Loading branch information
josancamon19 authored Sep 28, 2024
2 parents 4477581 + b5ccf01 commit 1c22cc0
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 21 deletions.
187 changes: 187 additions & 0 deletions app/lib/pages/settings/delete_account.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:friend_private/backend/http/api/users.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/main.dart';
import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:friend_private/widgets/dialog.dart';
import 'package:gradient_borders/gradient_borders.dart';

class DeleteAccount extends StatefulWidget {
const DeleteAccount({super.key});

@override
State<DeleteAccount> createState() => _DeleteAccountState();
}

class _DeleteAccountState extends State<DeleteAccount> {
bool checkboxValue = false;
bool isDeleteing = false;

void updateCheckboxValue(bool value) {
setState(() {
checkboxValue = value;
});
}

Future deleteAccountNow() async {
setState(() {
isDeleteing = true;
});
MixpanelManager().deleteAccountConfirmed();
MixpanelManager().deleteUser();
SharedPreferencesUtil().clear();
await deleteAccount();
await FirebaseAuth.instance.signOut();
setState(() {
isDeleteing = false;
});
routeToPage(context, const DeciderWidget(), replace: true);
}

@override
Widget build(BuildContext context) {
return PopScope(
canPop: isDeleteing,
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.primary,
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: const Text('Delete Account'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
const SizedBox(
height: 10,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 50),
child: Text(
"Are you sure you want to delete your account?",
style: TextStyle(
fontSize: 24,
),
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 10,
),
const Text(
"This cannot be undone.",
style: TextStyle(fontSize: 18),
),
const SizedBox(
height: 30,
),
const ListTile(
leading: Icon(Icons.message_rounded),
title: Text("All of your memories and conversations will be permanently erased."),
),
const ListTile(
leading: Icon(Icons.person_pin_outlined),
title: Text("Your plugins and integrations will be disconnected effectively immediately."),
),
const ListTile(
leading: Icon(Icons.upload_file_outlined),
title: Text(
"You can export your data before deleting your account, but once deleted, it cannot be recovered."),
),
const Spacer(),
Row(
children: [
Checkbox(
value: checkboxValue,
onChanged: (value) {
if (value != null) {
updateCheckboxValue(value);
}
},
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.80,
child: const Text(
"I understand that deleting my account is permanent and all data, including memories and conversations, will be lost and cannot be recovered. "),
),
],
),
const SizedBox(
height: 30,
),
isDeleteing
? const CircularProgressIndicator(
color: Colors.white,
)
: Container(
decoration: BoxDecoration(
border: const GradientBoxBorder(
gradient: LinearGradient(colors: [
Color.fromARGB(127, 208, 208, 208),
Color.fromARGB(127, 188, 99, 121),
Color.fromARGB(127, 86, 101, 182),
Color.fromARGB(127, 126, 190, 236)
]),
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
child: ElevatedButton(
onPressed: () {
if (checkboxValue) {
showDialog(
context: context,
builder: (c) {
return getDialog(context, () {
MixpanelManager().deleteAccountCancelled();
Navigator.of(context).pop();
}, () {
deleteAccountNow();
Navigator.of(context).pop();
}, "Are you sure?\n",
"This action is irreversible and will permanently delete your account and all associated data. Are you sure you want to proceed?",
okButtonText: 'Delete Now', cancelButtonText: 'Go Back');
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Check the box to confirm you understand that deleting your account is permanent and irreversible.'),
),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: const Color.fromARGB(255, 17, 17, 17),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Container(
width: double.infinity,
height: 45,
alignment: Alignment.center,
child: const Text(
'Delete Account',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 18,
color: Color.fromARGB(255, 255, 255, 255),
),
),
),
),
),
const SizedBox(
height: 70,
),
],
),
),
),
);
}
}
18 changes: 3 additions & 15 deletions app/lib/pages/settings/profile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import 'package:friend_private/pages/settings/recordings_storage_permission.dart
import 'package:friend_private/pages/speech_profile/page.dart';
import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:friend_private/widgets/dialog.dart';
import 'package:url_launcher/url_launcher.dart';

import 'delete_account.dart';

class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
Expand Down Expand Up @@ -179,19 +179,7 @@ class _ProfilePageState extends State<ProfilePage> {
),
onTap: () {
MixpanelManager().pageOpened('Profile Delete Account Dialog');
showDialog(
context: context,
builder: (ctx) {
return getDialog(
context,
() => Navigator.of(context).pop(),
() => launchUrl(Uri.parse('mailto:[email protected]?subject=Delete%20My%20Account')),
'Deleting Account?',
'Please send us an email at [email protected]',
okButtonText: 'Open Email',
singleButton: false,
);
});
Navigator.push(context, MaterialPageRoute(builder: (context) => const DeleteAccount()));
},
)
],
Expand Down
11 changes: 9 additions & 2 deletions app/lib/utils/analytics/mixpanel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ class MixpanelManager {

void bottomNavigationTabClicked(String tab) => track('Bottom Navigation Tab Clicked', properties: {'tab': tab});

void deviceConnected() =>
track('Device Connected', properties: {
void deviceConnected() => track('Device Connected', properties: {
..._preferences.btDeviceStruct.toJson(fwverAsString: true),
});

Expand Down Expand Up @@ -278,4 +277,12 @@ class MixpanelManager {
void assignedSegment(String assignType) => track('Assigned Segment $assignType');

void unassignedSegment() => track('Unassigned Segment');

void deleteAccountClicked() => track('Delete Account Clicked');

void deleteAccountConfirmed() => track('Delete Account Confirmed');

void deleteAccountCancelled() => track('Delete Account Cancelled');

void deleteUser() => _mixpanel?.getPeople().deleteUser();
}
3 changes: 2 additions & 1 deletion app/lib/widgets/dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ getDialog(
String content, {
bool singleButton = false,
String okButtonText = 'Ok',
String cancelButtonText = 'Cancel',
}) {
var actions = singleButton
? [
Expand All @@ -23,7 +24,7 @@ getDialog(
: [
TextButton(
onPressed: () => onCancel(),
child: const Text('Cancel', style: TextStyle(color: Colors.white)),
child: Text(cancelButtonText, style: TextStyle(color: Colors.white)),
),
TextButton(
onPressed: () => onConfirm(), child: Text(okButtonText, style: const TextStyle(color: Colors.white))),
Expand Down
31 changes: 31 additions & 0 deletions backend/database/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,34 @@ def update_person(uid: str, person_id: str, name: str):
def delete_person(uid: str, person_id: str):
person_ref = db.collection('users').document(uid).collection('people').document(person_id)
person_ref.update({'deleted': True})


def delete_user_data(uid: str):
user_ref = db.collection('users').document(uid)
memories_ref = user_ref.collection('memories')
# delete all memories
batch = db.batch()
for doc in memories_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete chat messages
messages_ref = user_ref.collection('messages')
batch = db.batch()
for doc in messages_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete facts
batch = db.batch()
facts_ref = user_ref.collection('facts')
for doc in facts_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete processing memories
processing_memories_ref = user_ref.collection('processing_memories')
batch = db.batch()
for doc in processing_memories_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete user
user_ref.delete()
return {'status': 'ok', 'message': 'Account deleted successfully'}
7 changes: 4 additions & 3 deletions backend/routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
@router.delete('/v1/users/delete-account', tags=['v1'])
def delete_account(uid: str = Depends(auth.get_current_user_uid)):
try:
# Set account as deleted in Firestore
update_user(uid, {"account_deleted": True})
# TODO: delete user data from the database
delete_user_data(uid)
# delete user from firebase auth
auth.delete_account(uid)
return {'status': 'ok', 'message': 'Account deleted successfully'}
except Exception as e:
print('delete_account', str(e))
raise HTTPException(status_code=500, detail=str(e))


Expand Down
5 changes: 5 additions & 0 deletions backend/utils/other/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ def measure_time(*args, **kw):
return result

return measure_time


def delete_account(uid: str):
auth.delete_user(uid)
return {"message": "User deleted"}

0 comments on commit 1c22cc0

Please sign in to comment.