CKT 1.0.2 | otorrinolaringologista
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import '../colors/app_colors.dart';
|
||||
|
||||
import '../main.dart' show supabase;
|
||||
import '../strings/common_strings.dart';
|
||||
import '../strings/settings_strings.dart';
|
||||
import '../widgets/app_dialogs.dart';
|
||||
import '../widgets/entrance.dart';
|
||||
@@ -41,9 +42,29 @@ class _SettingsBodyState extends State<SettingsBody> {
|
||||
);
|
||||
|
||||
if (confirmed != true) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final password = await _promptPassword(context);
|
||||
if (password == null || password.isEmpty) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final email = (supabase.auth.currentUser?.email ?? '').trim();
|
||||
if (email.isEmpty) return;
|
||||
|
||||
setState(() => _deletingAccount = true);
|
||||
try {
|
||||
// Reautentica com a palavra-passe introduzida antes de apagar nada —
|
||||
// sem esta verificação, qualquer pessoa com o telemóvel desbloqueado
|
||||
// (sessão já iniciada) conseguia apagar a conta sem confirmar que é
|
||||
// mesmo o dono.
|
||||
try {
|
||||
await supabase.auth.signInWithPassword(email: email, password: password);
|
||||
} catch (_) {
|
||||
if (mounted) showPillSnackBar(context, SettingsStrings.wrongPassword);
|
||||
return;
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
// A remoção de `auth.users` exige a service role key, que o app nunca
|
||||
// deve carregar — por isso corre numa Edge Function (server-side); ver
|
||||
// supabase/functions/delete-account. Sem isto, apagar só as linhas de
|
||||
@@ -146,7 +167,7 @@ class _SettingsBodyState extends State<SettingsBody> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_SectionLabel(SettingsStrings.dangerZone),
|
||||
_SectionLabel(SettingsStrings.userData),
|
||||
_SettingsCard(
|
||||
children: [
|
||||
_ActionTile(
|
||||
@@ -261,3 +282,91 @@ class _ActionTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pede a palavra-passe atual antes de uma ação irreversível (apagar dados
|
||||
/// da conta) — devolve a palavra-passe introduzida, ou `null` se cancelado.
|
||||
/// Só valida que o campo não está vazio; a palavra-passe em si é validada
|
||||
/// depois via `signInWithPassword`, pelo chamador.
|
||||
Future<String?> _promptPassword(BuildContext context) {
|
||||
final controller = TextEditingController();
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
var obscure = true;
|
||||
String? errorText;
|
||||
return StatefulBuilder(
|
||||
builder: (ctx, setState) {
|
||||
void submit() {
|
||||
if (controller.text.isEmpty) {
|
||||
setState(() => errorText = SettingsStrings.passwordRequired);
|
||||
return;
|
||||
}
|
||||
Navigator.of(ctx).pop(controller.text);
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
title: const Text(
|
||||
SettingsStrings.confirmPasswordTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontWeight: FontWeight.w900, color: _accentPink),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
SettingsStrings.confirmPasswordMessage,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.black.withValues(alpha: 0.72)),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obscure,
|
||||
autofocus: true,
|
||||
onSubmitted: (_) => submit(),
|
||||
decoration: InputDecoration(
|
||||
labelText: SettingsStrings.password,
|
||||
errorText: errorText,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscure
|
||||
? Icons.visibility_off_rounded
|
||||
: Icons.visibility_rounded,
|
||||
),
|
||||
onPressed: () => setState(() => obscure = !obscure),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: [
|
||||
TapBounce(
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(foregroundColor: _teal),
|
||||
onPressed: () => Navigator.of(ctx).pop(null),
|
||||
child: const Text(CommonStrings.cancel),
|
||||
),
|
||||
),
|
||||
TapBounce(
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _accentPink,
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(fontWeight: FontWeight.w800),
|
||||
),
|
||||
onPressed: submit,
|
||||
child: const Text(SettingsStrings.confirm),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user