Rejection retry flow with configurable wait days
- Usuario model: proRejectedAt parsed from professionals.updated_at - _StatusCard: shows countdown or retry button based on rejection_wait_days setting - Retry clears the rejected record via DELETE /professionals/me Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9634aa13ff
commit
391c32f765
@@ -11,6 +11,7 @@ class Usuario {
|
||||
final String? birthday;
|
||||
final String? gender;
|
||||
final ProState proState;
|
||||
final DateTime? proRejectedAt;
|
||||
final String? token;
|
||||
final bool isPhoneVerified;
|
||||
|
||||
@@ -26,6 +27,7 @@ class Usuario {
|
||||
required this.gender,
|
||||
required this.proState,
|
||||
required this.token,
|
||||
this.proRejectedAt,
|
||||
this.isPhoneVerified = false,
|
||||
});
|
||||
|
||||
@@ -57,11 +59,24 @@ class Usuario {
|
||||
birthday: doc['birthday'],
|
||||
gender: doc['gender'],
|
||||
proState: intToEnum((doc['professional_state'] as int?) ?? 0),
|
||||
proRejectedAt: _parseRejectedAt(doc),
|
||||
token: doc['token'],
|
||||
isPhoneVerified: doc['is_phone_verified'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
static DateTime? _parseRejectedAt(Map<String, dynamic> doc) {
|
||||
// /auth/me returns professionals nested; if pro_state==3 use professionals.updated_at
|
||||
final state = (doc['professional_state'] as int?) ?? 0;
|
||||
if (state != 3) return null;
|
||||
final pros = doc['professionals'];
|
||||
if (pros is Map) {
|
||||
final raw = pros['updated_at'];
|
||||
if (raw is String) return DateTime.tryParse(raw);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'User(id: $id, email: $email, phone: $phone, name: $name, nickname: $nickname, city: $city, picture: $picture, birthday: $birthday, gender: $gender, proState: $proState, token: $token)';
|
||||
|
||||
@@ -611,7 +611,7 @@ class _UploadTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _StatusCard extends StatelessWidget {
|
||||
class _StatusCard extends StatefulWidget {
|
||||
final Color cardBg;
|
||||
final Color border;
|
||||
final Color textPrimary;
|
||||
@@ -626,26 +626,62 @@ class _StatusCard extends StatelessWidget {
|
||||
required this.isPending,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_StatusCard> createState() => _StatusCardState();
|
||||
}
|
||||
|
||||
class _StatusCardState extends State<_StatusCard> {
|
||||
int _waitDays = 7;
|
||||
bool _loadingSettings = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!widget.isPending) _loadWaitDays();
|
||||
}
|
||||
|
||||
Future<void> _loadWaitDays() async {
|
||||
try {
|
||||
final data = await ApiService.instance.get('/settings') as Map<String, dynamic>;
|
||||
final v = data['rejection_wait_days'];
|
||||
if (v != null) setState(() => _waitDays = (v as num).toInt());
|
||||
} catch (_) {}
|
||||
if (mounted) setState(() => _loadingSettings = false);
|
||||
}
|
||||
|
||||
Future<void> _retry() async {
|
||||
try {
|
||||
await ApiService.instance.delete('/professionals/me');
|
||||
} catch (_) {}
|
||||
if (mounted) await context.read<AuthProvider>().isAuthenticated();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accent = isPending
|
||||
final accent = widget.isPending
|
||||
? const Color(0xFFF59E0B)
|
||||
: const Color(0xFFEF4444);
|
||||
|
||||
final user = context.watch<AuthProvider>().user;
|
||||
final rejectedAt = user?.proRejectedAt;
|
||||
final daysSince = rejectedAt != null
|
||||
? DateTime.now().difference(rejectedAt).inDays
|
||||
: _waitDays;
|
||||
final daysLeft = (_waitDays - daysSince).clamp(0, _waitDays);
|
||||
final canRetry = !widget.isPending && daysLeft == 0;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 24),
|
||||
padding: const EdgeInsets.all(32),
|
||||
decoration: BoxDecoration(
|
||||
color: cardBg,
|
||||
color: widget.cardBg,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: border),
|
||||
border: Border.all(color: widget.border),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (isPending)
|
||||
const Image(
|
||||
image: AssetImage('assets/checklist.gif'),
|
||||
width: 160)
|
||||
if (widget.isPending)
|
||||
const Image(image: AssetImage('assets/checklist.gif'), width: 160)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
@@ -653,63 +689,70 @@ class _StatusCard extends StatelessWidget {
|
||||
color: accent.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isPending
|
||||
? Icons.hourglass_empty_rounded
|
||||
: Icons.sentiment_dissatisfied_outlined,
|
||||
size: 52,
|
||||
color: accent),
|
||||
child: Icon(Icons.sentiment_dissatisfied_outlined, size: 52, color: accent),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
isPending
|
||||
? 'Solicitud en revisión'
|
||||
: 'Solicitud no aprobada',
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimary),
|
||||
widget.isPending ? 'Solicitud en revisión' : 'Solicitud no aprobada',
|
||||
style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold, color: widget.textPrimary),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
isPending
|
||||
widget.isPending
|
||||
? 'Estamos revisando tus datos. Te notificaremos cuando tu cuenta esté aprobada.'
|
||||
: 'Tu solicitud no fue aprobada. Revisa tus documentos y vuelve a intentarlo.',
|
||||
: canRetry
|
||||
? 'Puedes volver a enviar tu solicitud con los documentos corregidos.'
|
||||
: 'Tu solicitud no fue aprobada. Podrás reintentar en $daysLeft día${daysLeft == 1 ? '' : 's'}.',
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
TextStyle(color: textSec, fontSize: 13, height: 1.5),
|
||||
style: TextStyle(color: widget.textSec, fontSize: 13, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border:
|
||||
Border.all(color: accent.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isPending
|
||||
? Icons.hourglass_empty_rounded
|
||||
: Icons.cancel_outlined,
|
||||
color: accent,
|
||||
size: 16,
|
||||
if (!widget.isPending && !_loadingSettings) ...[
|
||||
if (canRetry)
|
||||
FilledButton.icon(
|
||||
onPressed: _retry,
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
label: const Text('Volver a solicitar'),
|
||||
style: FilledButton.styleFrom(backgroundColor: const Color(0xFF42A4EF)),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: accent.withOpacity(0.4)),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
isPending ? 'Revisión en proceso' : 'No aprobado',
|
||||
style: TextStyle(
|
||||
color: accent,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.timer_outlined, color: accent, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Disponible en $daysLeft día${daysLeft == 1 ? '' : 's'}',
|
||||
style: TextStyle(color: accent, fontWeight: FontWeight.w600, fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
] else if (widget.isPending)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: accent.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.hourglass_empty_rounded, color: accent, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text('Revisión en proceso',
|
||||
style: TextStyle(color: accent, fontWeight: FontWeight.w600, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user