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>
44 lines
1.5 KiB
Dart
44 lines
1.5 KiB
Dart
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);
|
|
});
|