EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode bayar, tukar EnakCoin, transfer - PIN (buat, ganti, lupa), outlet, riwayat order, game spin - Halaman community, webview, coming soon, dan ikon menu beranda baru - Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR - Hapus customer point loader, menu, dan checkout lama - Base URL ke enaklo-pos-api.altru.id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5954510b93
commit
f5e3075205
@@ -17,6 +17,7 @@ import 'interceptors/bad_request_interceptor.dart';
|
||||
import 'interceptors/connection_timeout_interceptor.dart';
|
||||
import 'interceptors/internal_server_interceptor.dart';
|
||||
import 'interceptors/not_found_interceptor.dart';
|
||||
import 'interceptors/sensitive_aware_log_interceptor.dart';
|
||||
import 'interceptors/unauthorized_interceptor.dart';
|
||||
|
||||
@lazySingleton
|
||||
@@ -37,10 +38,12 @@ class ApiClient {
|
||||
|
||||
if (kDebugMode) {
|
||||
_dio.interceptors.add(
|
||||
AwesomeDioInterceptor(
|
||||
logResponseHeaders: false,
|
||||
logRequestTimeout: false,
|
||||
logRequestHeaders: true,
|
||||
SensitiveAwareLogInterceptor(
|
||||
AwesomeDioInterceptor(
|
||||
logResponseHeaders: false,
|
||||
logRequestTimeout: false,
|
||||
logRequestHeaders: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -54,6 +57,7 @@ class ApiClient {
|
||||
bool followRedirects = true,
|
||||
bool Function(int?)? validateStatus,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? extra,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.post(
|
||||
@@ -64,6 +68,7 @@ class ApiClient {
|
||||
followRedirects: followRedirects,
|
||||
validateStatus: validateStatus,
|
||||
contentType: contentType,
|
||||
extra: extra,
|
||||
),
|
||||
queryParameters: params,
|
||||
);
|
||||
@@ -103,6 +108,7 @@ class ApiClient {
|
||||
bool followRedirects = true,
|
||||
bool Function(int?)? validateStatus,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? extra,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.get(
|
||||
@@ -112,6 +118,7 @@ class ApiClient {
|
||||
followRedirects: followRedirects,
|
||||
validateStatus: validateStatus,
|
||||
contentType: contentType,
|
||||
extra: extra,
|
||||
),
|
||||
queryParameters: params,
|
||||
);
|
||||
@@ -153,6 +160,7 @@ class ApiClient {
|
||||
bool Function(int?)? validateStatus,
|
||||
String? contentType,
|
||||
void Function(int count, int total)? onSendProgress,
|
||||
Map<String, dynamic>? extra,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.put(
|
||||
@@ -163,6 +171,7 @@ class ApiClient {
|
||||
followRedirects: followRedirects,
|
||||
validateStatus: validateStatus,
|
||||
contentType: contentType,
|
||||
extra: extra,
|
||||
),
|
||||
queryParameters: params,
|
||||
onSendProgress: onSendProgress,
|
||||
@@ -204,6 +213,7 @@ class ApiClient {
|
||||
bool followRedirects = true,
|
||||
bool Function(int?)? validateStatus,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? extra,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.delete(
|
||||
@@ -214,6 +224,7 @@ class ApiClient {
|
||||
followRedirects: followRedirects,
|
||||
validateStatus: validateStatus,
|
||||
contentType: contentType,
|
||||
extra: extra,
|
||||
),
|
||||
queryParameters: params,
|
||||
);
|
||||
|
||||
+517
-1374
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// Key untuk `Options.extra` yang menandai request berisi data rahasia
|
||||
/// (PIN, OTP). Request bertanda ini tidak dicetak ke log sama sekali.
|
||||
const String kSensitiveRequest = 'sensitive';
|
||||
|
||||
class SensitiveAwareLogInterceptor extends Interceptor {
|
||||
final Interceptor _logger;
|
||||
|
||||
SensitiveAwareLogInterceptor(this._logger);
|
||||
|
||||
bool _isSensitive(RequestOptions options) =>
|
||||
options.extra[kSensitiveRequest] == true;
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (_isSensitive(options)) return handler.next(options);
|
||||
_logger.onRequest(options, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
if (_isSensitive(response.requestOptions)) return handler.next(response);
|
||||
_logger.onResponse(response, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if (_isSensitive(err.requestOptions)) return handler.next(err);
|
||||
_logger.onError(err, handler);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import '../../sample/sample_data.dart';
|
||||
|
||||
class AppConstant {
|
||||
static const String appName = "Enaklo";
|
||||
static const String coinName = "EnakCoin";
|
||||
static const String poinName = "EnakPoin";
|
||||
static const String poinName = "EnakPoint";
|
||||
static const String gameUrl = "https://game.enaklo.com/";
|
||||
|
||||
/// `theme-color` game.enaklo.com.
|
||||
static const Color gameBackgroundColor = Color(0xFF09090B);
|
||||
}
|
||||
|
||||
MerchantModel merchant = merchants.first;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@module
|
||||
abstract class SecureStorageDi {
|
||||
@lazySingleton
|
||||
FlutterSecureStorage get secureStorage => const FlutterSecureStorage();
|
||||
}
|
||||
@@ -25,4 +25,7 @@ extension IntegerExt on int {
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
).format(this);
|
||||
|
||||
/// Format ribuan Indonesia tanpa simbol: 12.500
|
||||
String get thousandFormat => NumberFormat.decimalPattern('id').format(this);
|
||||
}
|
||||
|
||||
@@ -30,4 +30,13 @@ extension DateTimeIndonesia on DateTime {
|
||||
String get toHourMinute {
|
||||
return DateFormat('HH:mm', 'id_ID').format(this);
|
||||
}
|
||||
|
||||
/// Waktu dinding WIB (UTC+7), apa pun zona waktu perangkat.
|
||||
/// Hanya untuk ditampilkan; jangan dipakai untuk membandingkan waktu.
|
||||
DateTime get toWib => toUtc().add(const Duration(hours: 7));
|
||||
|
||||
/// Format: 13 Agu 2025 20:00
|
||||
String get toShortDatetime {
|
||||
return DateFormat('d MMM yyyy HH:mm', 'id_ID').format(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -18,6 +20,18 @@ String getNormalizePhone(String phoneNumber) {
|
||||
return '62$normalizedPhone';
|
||||
}
|
||||
|
||||
/// UUID v4 acak dari [Random.secure], dipakai sebagai `Idempotency-Key`
|
||||
/// dan `device_id`.
|
||||
String generateUuidV4() {
|
||||
final random = Random.secure();
|
||||
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
return '${hex.substring(0, 8)}-${hex.substring(8, 12)}-'
|
||||
'${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}';
|
||||
}
|
||||
|
||||
Map<String, dynamic> getAuthorizationHeader() {
|
||||
return {
|
||||
'Authorization':
|
||||
|
||||
@@ -8,7 +8,30 @@ class ApiPath {
|
||||
|
||||
// Marketing
|
||||
static String ferrisWheel = '/api/v1/customer/ferris-wheel';
|
||||
static String spin = '/api/v1/customer/spin';
|
||||
|
||||
// Customer
|
||||
static String customerPoint = '/api/v1/customer/points';
|
||||
// Wallet
|
||||
static String wallet = '/api/v1/customer/wallet';
|
||||
static String walletTransactions = '/api/v1/customer/wallet/transactions';
|
||||
static String walletExpiring = '/api/v1/customer/wallet/expiring';
|
||||
static String walletPaymentCode = '/api/v1/customer/wallet/payment-code';
|
||||
static String walletExchangePreview =
|
||||
'/api/v1/customer/wallet/exchange/preview';
|
||||
static String walletExchange = '/api/v1/customer/wallet/exchange';
|
||||
static String walletTransferRecipient =
|
||||
'/api/v1/customer/wallet/transfer/recipient';
|
||||
static String walletTransfer = '/api/v1/customer/wallet/transfer';
|
||||
|
||||
// Outlet & order
|
||||
static String outlets = '/api/v1/customer/outlets';
|
||||
static String orders = '/api/v1/customer/orders';
|
||||
|
||||
// PIN
|
||||
static String pin = '/api/v1/customer/pin';
|
||||
static String pinStatus = '/api/v1/customer/pin/status';
|
||||
static String pinOtp = '/api/v1/customer/pin/otp';
|
||||
static String pinReset = '/api/v1/customer/pin/reset';
|
||||
|
||||
// Device (push notification)
|
||||
static String devices = '/api/v1/customer/devices';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user