Mitra Profil: WA/TG contacts + Keluar-only (no Hapus Akun)
Replaces the placeholder "Hubungi Koordinator" row with two real
contacts pulled from backend config (support_handles_json), and drops
the "Hapus Akun" CTA. Mirrors the figma BestieProfile design but uses
the same WA/TG channel as the customer Tanya Admin sheet — business
decided the same ops team triages both audiences.
Backend:
- Promote support-handles route from /api/client to /api/shared
(renamed file + export). Both apps now consume the same endpoint;
hitting /api/client/* from mitra would violate the per-app
convention in mitra_app/CLAUDE.md.
- client_app provider updated to /api/shared/support-handles.
Mitra app:
- New support_handles_provider mirroring the client_app one. Adds a
`displayHandle` getter that strips the URL scheme for the subtitle
("https://wa.me/X" → "wa.me/X", "https://t.me/Y" → "t.me/Y") so the
row looks like the figma without exposing raw URLs.
- Profil screen now lists: Chat WhatsApp Kami, Chat Telegram Kami,
Syarat & Ketentuan, Kebijakan Privasi. Danger zone simplified to
Keluar only — mitras request account deletion through the same
WA/TG channels (no separate self-service path).
- url_launcher added as a runtime dep, launches deeplinks in
externalApplication mode with graceful snackbar fallback when
parsing or launching fails.
Updates [[feedback-mitra-internal-audience]] — pre-login rule still
holds (no admin CTAs on S3a/S3b/AccountInactive), but the post-login
Profil tab now does surface WA/TG. Overrides decided 2026-05-21.
Verified on emulator-5556: Profil tab renders both rows with handles
from `wa.me/6285173310010` + `t.me/halobestie`, Keluar present, no
Hapus Akun button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
43
mitra_app/lib/features/profile/support_handles_provider.dart
Normal file
43
mitra_app/lib/features/profile/support_handles_provider.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/api/api_client_provider.dart';
|
||||
|
||||
class SupportHandle {
|
||||
final String label;
|
||||
final String deeplink;
|
||||
const SupportHandle({required this.label, required this.deeplink});
|
||||
|
||||
factory SupportHandle.fromJson(Map<String, dynamic> json) =>
|
||||
SupportHandle(
|
||||
label: json['label'] as String? ?? '',
|
||||
deeplink: json['deeplink'] as String? ?? '',
|
||||
);
|
||||
|
||||
/// Display form for the menu subtitle: strip the URL scheme so
|
||||
/// "https://wa.me/6285173310010" → "wa.me/6285173310010" and
|
||||
/// "https://t.me/halobestie" → "t.me/halobestie". Falls back to the
|
||||
/// raw deeplink when there's no scheme to strip.
|
||||
String get displayHandle {
|
||||
if (deeplink.isEmpty) return '';
|
||||
final stripped = deeplink.replaceFirst(RegExp(r'^https?://'), '');
|
||||
return stripped;
|
||||
}
|
||||
}
|
||||
|
||||
class SupportHandles {
|
||||
final SupportHandle? wa;
|
||||
final SupportHandle? telegram;
|
||||
const SupportHandles({this.wa, this.telegram});
|
||||
|
||||
factory SupportHandles.fromJson(Map<String, dynamic> json) {
|
||||
SupportHandle? parse(dynamic v) =>
|
||||
v is Map<String, dynamic> ? SupportHandle.fromJson(v) : null;
|
||||
return SupportHandles(wa: parse(json['wa']), telegram: parse(json['telegram']));
|
||||
}
|
||||
}
|
||||
|
||||
final supportHandlesProvider = FutureProvider<SupportHandles>((ref) async {
|
||||
final api = ref.read(apiClientProvider);
|
||||
final response = await api.get('/api/shared/support-handles');
|
||||
final data = response['data'] as Map<String, dynamic>? ?? const {};
|
||||
return SupportHandles.fromJson(data);
|
||||
});
|
||||
Reference in New Issue
Block a user