payfrit-app/lib/screens/order_home_screen.dart
John Mizerek 33f7128b40 feat: implement user authentication with login screen
- Add LoginScreen with form validation and error handling
- Add Api.login() method with LoginResponse model
- Add login route to AppRouter
- Update SplashScreen to check auth status and route to login if needed
- Store auth token in Api service for authenticated requests
- Fix restaurant selection to work with authenticated users
2025-12-29 10:01:35 -08:00

303 lines
8.2 KiB
Dart

// lib/screens/order_home_screen.dart
import "package:flutter/material.dart";
import "../services/api.dart";
class OrderHomeScreen extends StatefulWidget {
// OPTIONAL so routes that call `const OrderHomeScreen()` compile.
final int? businessId;
final int? servicePointId;
final int? userId;
const OrderHomeScreen({
super.key,
this.businessId,
this.servicePointId,
this.userId,
});
@override
State<OrderHomeScreen> createState() => _OrderHomeScreenState();
}
class _OrderHomeScreenState extends State<OrderHomeScreen> {
bool _busy = false;
String? _error;
int? _businessId;
int? _servicePointId;
int? _userId;
int? _orderId;
int? _asIntNullable(dynamic v) {
if (v == null) return null;
if (v is int) return v;
if (v is double) return v.toInt();
if (v is String) {
final s = v.trim();
if (s.isEmpty) return null;
return int.tryParse(s);
}
return null;
}
void _loadIdsFromWidgetAndRoute() {
int? b = widget.businessId;
int? sp = widget.servicePointId;
int? u = widget.userId;
// If not provided via constructor, attempt to read from route arguments.
final args = ModalRoute.of(context)?.settings.arguments;
if ((b == null || sp == null || u == null) && args is Map) {
// Prefer exact key spellings that match labels.
b ??= _asIntNullable(args["BusinessID"]);
sp ??= _asIntNullable(args["ServicePointID"]);
u ??= _asIntNullable(args["UserID"]);
// Fallback keys (common Flutter style)
b ??= _asIntNullable(args["businessId"]);
sp ??= _asIntNullable(args["servicePointId"]);
u ??= _asIntNullable(args["userId"]);
}
// Normalize "0" to null (so we don't show misleading zeros).
if (b == 0) b = null;
if (sp == 0) sp = null;
if (u == 0) u = null;
final changed = (b != _businessId) || (sp != _servicePointId) || (u != _userId);
if (changed) {
setState(() {
_businessId = b;
_servicePointId = sp;
_userId = u;
});
}
}
@override
void initState() {
super.initState();
// cannot read ModalRoute here reliably; do it in didChangeDependencies.
_businessId = widget.businessId == 0 ? null : widget.businessId;
_servicePointId = widget.servicePointId == 0 ? null : widget.servicePointId;
_userId = widget.userId == 0 ? null : widget.userId;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_loadIdsFromWidgetAndRoute();
}
@override
void didUpdateWidget(covariant OrderHomeScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.businessId != widget.businessId ||
oldWidget.servicePointId != widget.servicePointId ||
oldWidget.userId != widget.userId) {
_loadIdsFromWidgetAndRoute();
}
}
Future<void> _run(Future<void> Function() fn) async {
if (_busy) return;
setState(() {
_busy = true;
_error = null;
});
try {
await fn();
} catch (e) {
setState(() => _error = e.toString());
} finally {
if (mounted) setState(() => _busy = false);
}
}
bool get _hasAllIds => _businessId != null && _servicePointId != null && _userId != null;
Future<void> _createOrLoadCart() async {
if (!_hasAllIds) {
setState(() {
_error = "Missing IDs. BusinessID / ServicePointID / UserID were not provided to this screen.";
});
return;
}
await _run(() async {
final cartData = await Api.getOrCreateCart(
userId: _userId!,
businessId: _businessId!,
servicePointId: _servicePointId!,
orderTypeId: 1, // MVP default: dine-in
);
final oid = _asIntNullable(cartData is Map ? cartData["OrderID"] : null);
setState(() => _orderId = oid);
});
}
Future<void> _refreshCart() async {
if (_orderId == null) return;
await _run(() async {
await Api.getCart(orderId: _orderId!);
});
}
Future<void> _addDemoItem(int itemId) async {
if (_orderId == null) {
setState(() => _error = "No cart yet. Tap 'Create/Load Cart' first.");
return;
}
await _run(() async {
await Api.setLineItem(
orderId: _orderId!,
parentOrderLineItemId: 0,
itemId: itemId,
qty: 1,
selectedChildItemIds: const <int>[],
);
});
}
Future<void> _submit() async {
if (_orderId == null) {
setState(() => _error = "No cart yet.");
return;
}
await _run(() async {
await Api.submitOrder(orderId: _orderId!);
});
}
Widget _idRow(String label, int? value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
SizedBox(
width: 120,
child: Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
),
Expanded(
child: Text(
value == null ? "(missing)" : value.toString(),
textAlign: TextAlign.right,
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final orderId = _orderId;
return Scaffold(
appBar: AppBar(title: const Text("Order (Compile-Only MVP)")),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text(
"This screen exists only to compile cleanly against\n"
"Api.dart.\n"
"No menu, no models, no polish.",
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
_idRow("BusinessID", _businessId),
_idRow("ServicePointID", _servicePointId),
_idRow("UserID", _userId),
],
),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _busy ? null : _createOrLoadCart,
child: const Text("Create/Load Cart"),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: (_busy || orderId == null) ? null : _refreshCart,
child: const Text("Refresh Cart"),
),
),
],
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Cart OrderID: ${orderId ?? "(none)"}"),
const SizedBox(height: 10),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
ElevatedButton(
onPressed: _busy ? null : () => _addDemoItem(101),
child: const Text("Add Demo Item 101"),
),
ElevatedButton(
onPressed: _busy ? null : () => _addDemoItem(102),
child: const Text("Add Demo Item 102"),
),
ElevatedButton(
onPressed: _busy ? null : () => _addDemoItem(103),
child: const Text("Add Demo Item 103"),
),
],
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: (_busy || orderId == null) ? null : _submit,
child: const Text("Submit Order"),
),
],
),
),
),
const SizedBox(height: 16),
if (_busy) ...[
const Center(child: CircularProgressIndicator()),
const SizedBox(height: 16),
],
if (_error != null)
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(_error!),
),
),
],
),
);
}
}