payfrit-app/lib/services/api.dart
John Mizerek 2491c961e0 Add address management and user account features
- Add delivery address list, add, edit, delete, set default functionality
- Add order history screen
- Add profile settings screen
- Add account screen with avatar upload
- Update restaurant select gradient direction
- Add states API endpoint for address forms
- Fix table names (tt_States)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 20:22:37 -08:00

907 lines
26 KiB
Dart

import "dart:convert";
import "package:http/http.dart" as http;
import "../models/cart.dart";
import "../models/menu_item.dart";
import "../models/order_history.dart";
import "../models/restaurant.dart";
import "../models/service_point.dart";
import "../models/user_profile.dart";
import "auth_storage.dart";
class ApiRawResponse {
final int statusCode;
final String rawBody;
final Map<String, dynamic>? json;
const ApiRawResponse({
required this.statusCode,
required this.rawBody,
required this.json,
});
}
class LoginResponse {
final int userId;
final String userFirstName;
final String token;
const LoginResponse({
required this.userId,
required this.userFirstName,
required this.token,
});
factory LoginResponse.fromJson(Map<String, dynamic> json) {
return LoginResponse(
userId: (json["UserID"] as num).toInt(),
userFirstName: (json["UserFirstName"] as String?) ?? "",
token: (json["Token"] as String?) ?? "",
);
}
}
class Api {
static String? _userToken;
// MVP hardcode
static int _mvpBusinessId = 17;
static void setAuthToken(String? token) => _userToken = token;
static void clearAuthToken() => _userToken = null;
static void setBusinessId(int? businessId) {
if (businessId != null && businessId > 0) {
_mvpBusinessId = businessId;
}
}
static void clearCookies() {
// no-op
}
static String get baseUrl {
const v = String.fromEnvironment("PAYFRIT_API_BASE_URL");
if (v.isEmpty) {
return "https://biz.payfrit.com/api";
}
return v;
}
static Uri _u(String path) {
final b = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
final p = path.startsWith("/") ? path : "/$path";
return Uri.parse("$b$p");
}
static Map<String, String> _headers({required bool json, int? businessIdOverride}) {
final h = <String, String>{};
if (json) h["Content-Type"] = "application/json; charset=utf-8";
final tok = _userToken;
if (tok != null && tok.isNotEmpty) {
h["X-User-Token"] = tok;
}
final int bid = (businessIdOverride != null && businessIdOverride > 0) ? businessIdOverride : _mvpBusinessId;
h["X-Business-ID"] = bid.toString();
return h;
}
static Map<String, dynamic>? _tryDecodeJsonMap(String body) {
try {
final decoded = jsonDecode(body);
if (decoded is Map<String, dynamic>) return decoded;
} catch (_) {
// If JSON parsing fails, try to extract JSON from the body
// (handles cases where debug output is appended after JSON)
final jsonStart = body.indexOf('{');
final jsonEnd = body.lastIndexOf('}');
if (jsonStart >= 0 && jsonEnd > jsonStart) {
try {
final jsonPart = body.substring(jsonStart, jsonEnd + 1);
final decoded = jsonDecode(jsonPart);
if (decoded is Map<String, dynamic>) return decoded;
} catch (_) {}
}
}
return null;
}
static Future<ApiRawResponse> _getRaw(String path, {int? businessIdOverride}) async {
final url = _u(path);
final resp = await http.get(url, headers: _headers(json: false, businessIdOverride: businessIdOverride));
final body = resp.body;
final j = _tryDecodeJsonMap(body);
return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j);
}
static Future<ApiRawResponse> _postRaw(
String path,
Map<String, dynamic> payload, {
int? businessIdOverride,
}) async {
final url = _u(path);
final resp = await http.post(
url,
headers: _headers(json: true, businessIdOverride: businessIdOverride),
body: jsonEncode(payload),
);
final body = resp.body;
final j = _tryDecodeJsonMap(body);
return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j);
}
static bool _ok(Map<String, dynamic> j) => j["OK"] == true || j["ok"] == true;
static String _err(Map<String, dynamic> j) => (j["ERROR"] ?? j["error"] ?? "").toString();
static List<dynamic>? _pickArray(Map<String, dynamic> j, List<String> keys) {
for (final k in keys) {
final v = j[k];
if (v is List) return v;
}
return null;
}
static Map<String, dynamic> _requireJson(ApiRawResponse raw, String label) {
final j = raw.json;
if (j == null) {
throw StateError("$label request failed: ${raw.statusCode}\nNon-JSON response.");
}
return j;
}
// -------------------------
// Authentication
// -------------------------
static Future<LoginResponse> login({
required String username,
required String password,
}) async {
final raw = await _postRaw(
"/auth/login.cfm",
{
"username": username,
"password": password,
},
);
final j = _requireJson(raw, "Login");
if (!_ok(j)) {
final err = _err(j);
if (err == "bad_credentials") {
throw StateError("Invalid email/phone or password");
} else if (err == "missing_fields") {
throw StateError("Username and password are required");
} else {
throw StateError("Login failed: $err");
}
}
final response = LoginResponse.fromJson(j);
// Store token for future requests
setAuthToken(response.token);
return response;
}
static Future<void> logout() async {
setAuthToken(null);
clearCookies();
await AuthStorage.clearAuth();
}
// -------------------------
// Businesses (legacy model name: Restaurant)
// -------------------------
static Future<ApiRawResponse> listRestaurantsRaw() async {
return _getRaw("/businesses/list.cfm", businessIdOverride: _mvpBusinessId);
}
static Future<List<Restaurant>> listRestaurants() async {
final raw = await listRestaurantsRaw();
final j = _requireJson(raw, "Businesses");
if (!_ok(j)) {
throw StateError(
"Businesses API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
final arr = _pickArray(j, const ["Businesses", "BUSINESSES"]);
if (arr == null) {
throw StateError("Businesses JSON missing Businesses array.\nRaw: ${raw.rawBody}");
}
final out = <Restaurant>[];
for (final e in arr) {
if (e is Map<String, dynamic>) {
out.add(Restaurant.fromJson(e));
} else if (e is Map) {
out.add(Restaurant.fromJson(e.cast<String, dynamic>()));
}
}
return out;
}
// -------------------------
// Service Points
// -------------------------
static Future<List<ServicePoint>> listServicePoints({required int businessId}) async {
// CRITICAL: endpoint is behaving like it reads JSON body, not query/header.
final raw = await _postRaw(
"/servicepoints/list.cfm",
{"BusinessID": businessId},
businessIdOverride: businessId,
);
final j = _requireJson(raw, "ServicePoints");
if (!_ok(j)) {
throw StateError(
"ServicePoints API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
final arr = _pickArray(j, const ["ServicePoints", "SERVICEPOINTS"]);
if (arr == null) {
throw StateError("ServicePoints JSON missing ServicePoints array.\nRaw: ${raw.rawBody}");
}
final out = <ServicePoint>[];
for (final e in arr) {
if (e is Map<String, dynamic>) {
out.add(ServicePoint.fromJson(e));
} else if (e is Map) {
out.add(ServicePoint.fromJson(e.cast<String, dynamic>()));
}
}
return out;
}
// -------------------------
// Menu Items
// -------------------------
static Future<List<MenuItem>> listMenuItems({required int businessId}) async {
final raw = await _postRaw(
"/menu/items.cfm",
{"BusinessID": businessId},
businessIdOverride: businessId,
);
final j = _requireJson(raw, "MenuItems");
if (!_ok(j)) {
throw StateError(
"MenuItems API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
final arr = _pickArray(j, const ["Items", "ITEMS"]);
if (arr == null) {
throw StateError("MenuItems JSON missing Items array.\nRaw: ${raw.rawBody}");
}
final out = <MenuItem>[];
for (final e in arr) {
if (e is Map<String, dynamic>) {
out.add(MenuItem.fromJson(e));
} else if (e is Map) {
out.add(MenuItem.fromJson(e.cast<String, dynamic>()));
}
}
return out;
}
// -------------------------
// Cart & Orders
// -------------------------
static Future<Cart> getOrCreateCart({
required int userId,
required int businessId,
required int servicePointId,
required int orderTypeId,
}) async {
final raw = await _postRaw(
"/orders/getOrCreateCart.cfm",
{
"OrderUserID": userId,
"BusinessID": businessId,
"OrderServicePointID": servicePointId,
"OrderTypeID": orderTypeId,
},
businessIdOverride: businessId,
);
final j = _requireJson(raw, "GetOrCreateCart");
if (!_ok(j)) {
throw StateError(
"GetOrCreateCart API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["MESSAGE"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
);
}
return Cart.fromJson(j);
}
static Future<Cart> getCart({required int orderId}) async {
final raw = await _postRaw(
"/orders/getCart.cfm",
{"OrderID": orderId},
);
final j = _requireJson(raw, "GetCart");
if (!_ok(j)) {
throw StateError(
"GetCart API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
return Cart.fromJson(j);
}
static Future<Cart> setLineItem({
required int orderId,
required int parentOrderLineItemId,
required int itemId,
required bool isSelected,
int quantity = 1,
String? remark,
}) async {
final raw = await _postRaw(
"/orders/setLineItem.cfm",
{
"OrderID": orderId,
"ParentOrderLineItemID": parentOrderLineItemId,
"ItemID": itemId,
"IsSelected": isSelected,
"Quantity": quantity,
if (remark != null && remark.isNotEmpty) "Remark": remark,
},
);
final j = _requireJson(raw, "SetLineItem");
print('[API] setLineItem response: OK=${j["OK"]}, ERROR=${_err(j)}, orderId=$orderId, itemId=$itemId, parentLI=$parentOrderLineItemId');
if (!_ok(j)) {
throw StateError(
"SetLineItem failed: ${_err(j)} - ${(j["MESSAGE"] ?? "").toString()}",
);
}
return Cart.fromJson(j);
}
/// Set the order type (delivery/takeaway) on an existing cart
static Future<Cart> setOrderType({
required int orderId,
required int orderTypeId,
int? addressId,
}) async {
final raw = await _postRaw(
"/orders/setOrderType.cfm",
{
"OrderID": orderId,
"OrderTypeID": orderTypeId,
if (addressId != null) "AddressID": addressId,
},
);
final j = _requireJson(raw, "SetOrderType");
if (!_ok(j)) {
throw StateError(
"SetOrderType failed: ${_err(j)} - ${(j["MESSAGE"] ?? "").toString()}",
);
}
return Cart.fromJson(j);
}
static Future<void> submitOrder({required int orderId}) async {
final raw = await _postRaw(
"/orders/submit.cfm",
{"OrderID": orderId},
);
final j = _requireJson(raw, "SubmitOrder");
if (!_ok(j)) {
throw StateError(
"SubmitOrder API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
}
static Future<Map<String, dynamic>> checkOrderStatus({
required int orderId,
required int lastKnownStatusId,
}) async {
final raw = await _postRaw(
"/orders/checkStatusUpdate.cfm",
{
"OrderID": orderId,
"LastKnownStatusID": lastKnownStatusId,
},
);
final j = _requireJson(raw, "CheckOrderStatus");
if (!_ok(j)) {
throw StateError(
"CheckOrderStatus API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
return j;
}
// -------------------------
// Beacons
// -------------------------
static Future<Map<String, int>> listAllBeacons() async {
final raw = await _getRaw("/beacons/list_all.cfm");
final j = _requireJson(raw, "ListAllBeacons");
if (!_ok(j)) {
throw StateError(
"ListAllBeacons API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
final arr = _pickArray(j, const ["items", "ITEMS"]);
if (arr == null) return {};
final Map<String, int> uuidToBeaconId = {};
for (final e in arr) {
if (e is! Map) continue;
final item = e is Map<String, dynamic> ? e : e.cast<String, dynamic>();
final uuid = (item["BeaconUUID"] ?? item["BEACONUUID"] ?? "").toString().trim();
final beaconId = item["BeaconID"] ?? item["BEACONID"];
if (uuid.isNotEmpty && beaconId is num) {
uuidToBeaconId[uuid] = beaconId.toInt();
}
}
return uuidToBeaconId;
}
static Future<BeaconBusinessMapping> getBusinessFromBeacon({
required int beaconId,
}) async {
final raw = await _postRaw(
"/beacons/getBusinessFromBeacon.cfm",
{"BeaconID": beaconId},
);
final j = _requireJson(raw, "GetBusinessFromBeacon");
if (!_ok(j)) {
throw StateError(
"GetBusinessFromBeacon API returned OK=false\nERROR: ${_err(j)}\nHTTP Status: ${raw.statusCode}",
);
}
final beacon = j["BEACON"] as Map<String, dynamic>? ?? {};
final business = j["BUSINESS"] as Map<String, dynamic>? ?? {};
final servicePoint = j["SERVICEPOINT"] as Map<String, dynamic>? ?? {};
return BeaconBusinessMapping(
beaconId: _parseInt(beacon["BeaconID"]) ?? 0,
beaconName: (beacon["BeaconName"] as String?) ?? "",
businessId: _parseInt(business["BusinessID"]) ?? 0,
businessName: (business["BusinessName"] as String?) ?? "",
servicePointId: _parseInt(servicePoint["ServicePointID"]) ?? 0,
servicePointName: (servicePoint["ServicePointName"] as String?) ?? "",
);
}
static int? _parseInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String) {
if (value.isEmpty) return null;
return int.tryParse(value);
}
return null;
}
/// Check if user has pending orders at a business (for pickup detection)
static Future<List<PendingOrder>> getPendingOrdersForUser({
required int userId,
required int businessId,
}) async {
final raw = await _getRaw(
"/orders/getPendingForUser.cfm?UserID=$userId&BusinessID=$businessId",
);
final j = _requireJson(raw, "GetPendingOrdersForUser");
if (!_ok(j)) {
return []; // Return empty list on error
}
final arr = _pickArray(j, const ["ORDERS", "orders"]);
if (arr == null) return [];
return arr.map((e) {
final item = e is Map<String, dynamic> ? e : (e as Map).cast<String, dynamic>();
return PendingOrder.fromJson(item);
}).toList();
}
/// Get list of states/provinces for address forms
static Future<List<StateInfo>> getStates() async {
final raw = await _getRaw("/addresses/states.cfm");
final j = _requireJson(raw, "GetStates");
if (!_ok(j)) {
return [];
}
final arr = _pickArray(j, const ["STATES", "states"]);
if (arr == null) return [];
return arr.map((e) {
final item = e is Map<String, dynamic> ? e : (e as Map).cast<String, dynamic>();
return StateInfo.fromJson(item);
}).toList();
}
/// Get user's delivery addresses
static Future<List<DeliveryAddress>> getDeliveryAddresses() async {
final raw = await _getRaw("/addresses/list.cfm");
final j = _requireJson(raw, "GetDeliveryAddresses");
if (!_ok(j)) {
return [];
}
final arr = _pickArray(j, const ["ADDRESSES", "addresses"]);
if (arr == null) return [];
return arr.map((e) {
final item = e is Map<String, dynamic> ? e : (e as Map).cast<String, dynamic>();
return DeliveryAddress.fromJson(item);
}).toList();
}
/// Add a new delivery address
static Future<DeliveryAddress> addDeliveryAddress({
required String line1,
required String city,
required int stateId,
required String zipCode,
String? line2,
String? label,
bool setAsDefault = false,
}) async {
final raw = await _postRaw("/addresses/add.cfm", {
"Line1": line1,
"City": city,
"StateID": stateId,
"ZIPCode": zipCode,
if (line2 != null) "Line2": line2,
if (label != null) "Label": label,
"SetAsDefault": setAsDefault,
});
final j = _requireJson(raw, "AddDeliveryAddress");
if (!_ok(j)) {
throw StateError("AddDeliveryAddress failed: ${_err(j)}");
}
final addressData = j["ADDRESS"] as Map<String, dynamic>? ?? {};
return DeliveryAddress.fromJson(addressData);
}
/// Delete a delivery address
static Future<void> deleteDeliveryAddress(int addressId) async {
final raw = await _postRaw("/addresses/delete.cfm", {
"AddressID": addressId,
});
final j = _requireJson(raw, "DeleteDeliveryAddress");
if (!_ok(j)) {
throw StateError("DeleteDeliveryAddress failed: ${_err(j)}");
}
}
/// Set an address as the default delivery address
static Future<void> setDefaultDeliveryAddress(int addressId) async {
final raw = await _postRaw("/addresses/setDefault.cfm", {
"AddressID": addressId,
});
final j = _requireJson(raw, "SetDefaultDeliveryAddress");
if (!_ok(j)) {
throw StateError("SetDefaultDeliveryAddress failed: ${_err(j)}");
}
}
/// Get user's avatar URL
static Future<AvatarInfo> getAvatar() async {
print('[API] getAvatar: token=${_userToken != null ? "${_userToken!.substring(0, 8)}..." : "NULL"}');
final raw = await _getRaw("/auth/avatar.cfm");
print('[API] getAvatar response: ${raw.rawBody}');
final j = _requireJson(raw, "GetAvatar");
if (!_ok(j)) {
return const AvatarInfo(hasAvatar: false, avatarUrl: null);
}
return AvatarInfo(
hasAvatar: j["HAS_AVATAR"] == true,
avatarUrl: j["AVATAR_URL"] as String?,
);
}
/// Debug: Check token status with server
static Future<void> debugCheckToken() async {
try {
final raw = await _getRaw("/debug/checkToken.cfm");
print('[API] debugCheckToken response: ${raw.rawBody}');
} catch (e) {
print('[API] debugCheckToken error: $e');
}
}
/// Upload user avatar image
static Future<String> uploadAvatar(String filePath) async {
// First check token status
await debugCheckToken();
final uri = _u("/auth/avatar.cfm");
final request = http.MultipartRequest("POST", uri);
// Add auth headers
final tok = _userToken;
print('[API] uploadAvatar: token=${tok != null ? "${tok.substring(0, 8)}..." : "NULL"}');
if (tok != null && tok.isNotEmpty) {
request.headers["X-User-Token"] = tok;
}
request.headers["X-Business-ID"] = _mvpBusinessId.toString();
// Add the file
request.files.add(await http.MultipartFile.fromPath("avatar", filePath));
final streamedResponse = await request.send();
final response = await http.Response.fromStream(streamedResponse);
print('[API] uploadAvatar response: ${response.statusCode} - ${response.body}');
final j = _tryDecodeJsonMap(response.body);
if (j == null) {
throw StateError("UploadAvatar: Invalid JSON response");
}
if (!_ok(j)) {
throw StateError("UploadAvatar failed: ${_err(j)}");
}
return j["AVATAR_URL"] as String? ?? "";
}
/// Get order history for current user
static Future<OrderHistoryResponse> getOrderHistory({int limit = 20, int offset = 0}) async {
print('[API] getOrderHistory: token=${_userToken != null ? "${_userToken!.substring(0, 8)}..." : "NULL"}');
final raw = await _getRaw("/orders/history.cfm?limit=$limit&offset=$offset");
print('[API] getOrderHistory response: ${raw.rawBody.substring(0, raw.rawBody.length > 200 ? 200 : raw.rawBody.length)}');
final j = _requireJson(raw, "GetOrderHistory");
if (!_ok(j)) {
throw StateError("GetOrderHistory failed: ${_err(j)}");
}
final ordersJson = j["ORDERS"] as List<dynamic>? ?? [];
final orders = ordersJson
.map((e) => OrderHistoryItem.fromJson(e as Map<String, dynamic>))
.toList();
return OrderHistoryResponse(
orders: orders,
totalCount: (j["TOTAL_COUNT"] as num?)?.toInt() ?? orders.length,
);
}
/// Get user profile
static Future<UserProfile> getProfile() async {
final raw = await _getRaw("/auth/profile.cfm");
final j = _requireJson(raw, "GetProfile");
if (!_ok(j)) {
throw StateError("GetProfile failed: ${_err(j)}");
}
final userData = j["USER"] as Map<String, dynamic>? ?? {};
return UserProfile.fromJson(userData);
}
/// Update user profile
static Future<UserProfile> updateProfile({String? firstName, String? lastName}) async {
final body = <String, dynamic>{};
if (firstName != null) body["firstName"] = firstName;
if (lastName != null) body["lastName"] = lastName;
final raw = await _postRaw("/auth/profile.cfm", body);
final j = _requireJson(raw, "UpdateProfile");
if (!_ok(j)) {
throw StateError("UpdateProfile failed: ${_err(j)}");
}
final userData = j["USER"] as Map<String, dynamic>? ?? {};
return UserProfile.fromJson(userData);
}
}
class OrderHistoryResponse {
final List<OrderHistoryItem> orders;
final int totalCount;
const OrderHistoryResponse({
required this.orders,
required this.totalCount,
});
}
class BeaconBusinessMapping {
final int beaconId;
final String beaconName;
final int businessId;
final String businessName;
final int servicePointId;
final String servicePointName;
const BeaconBusinessMapping({
required this.beaconId,
required this.beaconName,
required this.businessId,
required this.businessName,
required this.servicePointId,
required this.servicePointName,
});
}
class PendingOrder {
final int orderId;
final String orderUuid;
final int orderTypeId;
final String orderTypeName;
final int statusId;
final String statusName;
final String submittedOn;
final int servicePointId;
final String servicePointName;
final String businessName;
final double subtotal;
const PendingOrder({
required this.orderId,
required this.orderUuid,
required this.orderTypeId,
required this.orderTypeName,
required this.statusId,
required this.statusName,
required this.submittedOn,
required this.servicePointId,
required this.servicePointName,
required this.businessName,
required this.subtotal,
});
factory PendingOrder.fromJson(Map<String, dynamic> json) {
return PendingOrder(
orderId: (json["OrderID"] ?? json["ORDERID"] ?? 0) as int,
orderUuid: (json["OrderUUID"] ?? json["ORDERUUID"] ?? "") as String,
orderTypeId: (json["OrderTypeID"] ?? json["ORDERTYPEID"] ?? 0) as int,
orderTypeName: (json["OrderTypeName"] ?? json["ORDERTYPENAME"] ?? "") as String,
statusId: (json["OrderStatusID"] ?? json["ORDERSTATUSID"] ?? 0) as int,
statusName: (json["StatusName"] ?? json["STATUSNAME"] ?? "") as String,
submittedOn: (json["SubmittedOn"] ?? json["SUBMITTEDON"] ?? "") as String,
servicePointId: (json["ServicePointID"] ?? json["SERVICEPOINTID"] ?? 0) as int,
servicePointName: (json["ServicePointName"] ?? json["SERVICEPOINTNAME"] ?? "") as String,
businessName: (json["BusinessName"] ?? json["BUSINESSNAME"] ?? "") as String,
subtotal: ((json["Subtotal"] ?? json["SUBTOTAL"] ?? 0) as num).toDouble(),
);
}
bool get isReady => statusId == 3;
bool get isPreparing => statusId == 2;
bool get isSubmitted => statusId == 1;
}
class DeliveryAddress {
final int addressId;
final String label;
final bool isDefault;
final String line1;
final String line2;
final String city;
final int stateId;
final String stateAbbr;
final String stateName;
final String zipCode;
final String displayText;
const DeliveryAddress({
required this.addressId,
required this.label,
required this.isDefault,
required this.line1,
required this.line2,
required this.city,
required this.stateId,
required this.stateAbbr,
required this.stateName,
required this.zipCode,
required this.displayText,
});
factory DeliveryAddress.fromJson(Map<String, dynamic> json) {
return DeliveryAddress(
addressId: (json["AddressID"] ?? json["ADDRESSID"] ?? 0) as int,
label: (json["Label"] ?? json["LABEL"] ?? "Address") as String,
isDefault: (json["IsDefault"] ?? json["ISDEFAULT"] ?? false) == true,
line1: (json["Line1"] ?? json["LINE1"] ?? "") as String,
line2: (json["Line2"] ?? json["LINE2"] ?? "") as String,
city: (json["City"] ?? json["CITY"] ?? "") as String,
stateId: (json["StateID"] ?? json["STATEID"] ?? 0) as int,
stateAbbr: (json["StateAbbr"] ?? json["STATEABBR"] ?? "") as String,
stateName: (json["StateName"] ?? json["STATENAME"] ?? "") as String,
zipCode: (json["ZIPCode"] ?? json["ZIPCODE"] ?? "") as String,
displayText: (json["DisplayText"] ?? json["DISPLAYTEXT"] ?? "") as String,
);
}
}
class AvatarInfo {
final bool hasAvatar;
final String? avatarUrl;
const AvatarInfo({
required this.hasAvatar,
required this.avatarUrl,
});
}
class StateInfo {
final int stateId;
final String abbr;
final String name;
const StateInfo({
required this.stateId,
required this.abbr,
required this.name,
});
factory StateInfo.fromJson(Map<String, dynamic> json) {
return StateInfo(
stateId: (json["StateID"] ?? json["STATEID"] ?? 0) as int,
abbr: (json["Abbr"] ?? json["ABBR"] ?? "") as String,
name: (json["Name"] ?? json["NAME"] ?? "") as String,
);
}
}