feat(shopinbit): step navigation and category selection UX
What changed, and why it matters
This commit is a routine user-experience update for the ShopInBit shopping feature. It re-enables a category-selection screen that had been skipped, resets the category choice when that screen opens, and adds a clearer placeholder message when the user is requesting a car. There is no security-relevant change.
No security action required. Treat as normal feature/UX commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies three Dart UI files in the ShopInBit flow. Step 1 now navigates to Step 2 instead of jumping directly to Step 3. Step 2 now resets widget.model.category to null in initState and updates the category list item styling. Step 4 adds an assert and a StateError throw for a null category, plus a car-specific description placeholder. These are all defensive/UX changes, not security fixes.
Changed components
lib/pages/shopinbit/shopinbit_step_1.dartlib/pages/shopinbit/shopinbit_step_2.dartlib/pages/shopinbit/shopinbit_step_4.dartInspect captured patch +37 / −12
diff --git a/lib/pages/shopinbit/shopinbit_step_1.dart b/lib/pages/shopinbit/shopinbit_step_1.dart
index c7b35e4..5bdb7a0 100644
--- a/lib/pages/shopinbit/shopinbit_step_1.dart
+++ b/lib/pages/shopinbit/shopinbit_step_1.dart
@@ -12,7 +12,7 @@ import '../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../widgets/desktop/primary_button.dart';
import '../../widgets/stack_text_field.dart';
import '../exchange_view/sub_widgets/step_row.dart';
-import 'shopinbit_step_3.dart';
+import 'shopinbit_step_2.dart';
class ShopInBitStep1 extends StatefulWidget {
const ShopInBitStep1({super.key, required this.model});
@@ -51,18 +51,16 @@ class _ShopInBitStep1State extends State<ShopInBitStep1> {
void _continue() {
widget.model.displayName = _nameController.text.trim();
- // Skip step 2 (category selection): only concierge is available initially
- widget.model.category = ShopInBitCategory.concierge;
if (Util.isDesktop) {
Navigator.of(context, rootNavigator: true).pop();
showDialog<void>(
context: context,
- builder: (_) => ShopInBitStep3(model: widget.model),
+ builder: (_) => ShopInBitStep2(model: widget.model),
);
} else {
Navigator.of(
context,
- ).pushNamed(ShopInBitStep3.routeName, arguments: widget.model);
+ ).pushNamed(ShopInBitStep2.routeName, arguments: widget.model);
}
}
diff --git a/lib/pages/shopinbit/shopinbit_step_2.dart b/lib/pages/shopinbit/shopinbit_step_2.dart
index c7aaf82..31abd1d 100644
--- a/lib/pages/shopinbit/shopinbit_step_2.dart
+++ b/lib/pages/shopinbit/shopinbit_step_2.dart
@@ -31,7 +31,9 @@ class _ShopInBitStep2State extends State<ShopInBitStep2> {
@override
void initState() {
super.initState();
- _selected = widget.model.category;
+ // Reset category selection.
+ widget.model.category = null;
+ _selected = null;
}
void _continue() {
@@ -73,10 +75,25 @@ class _ShopInBitStep2State extends State<ShopInBitStep2> {
padding: EdgeInsets.all(isDesktop ? 20 : 16),
child: Row(
children: [
- SvgPicture.asset(
- iconAsset,
- width: isDesktop ? 32 : 24,
- height: isDesktop ? 32 : 24,
+ Container(
+ width: isDesktop ? 48 : 40,
+ height: isDesktop ? 48 : 40,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorBlue
+ .withOpacity(0.1),
+ ),
+ alignment: Alignment.center,
+ child: SvgPicture.asset(
+ iconAsset,
+ width: isDesktop ? 24 : 20,
+ height: isDesktop ? 24 : 20,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorBlue,
+ ),
),
SizedBox(width: isDesktop ? 16 : 12),
Expanded(
diff --git a/lib/pages/shopinbit/shopinbit_step_4.dart b/lib/pages/shopinbit/shopinbit_step_4.dart
index 44e17d2..05577b8 100644
--- a/lib/pages/shopinbit/shopinbit_step_4.dart
+++ b/lib/pages/shopinbit/shopinbit_step_4.dart
@@ -214,11 +214,16 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
final service = ShopInBitService.instance;
final customerKey = await service.ensureCustomerKey();
+ assert(
+ widget.model.category != null,
+ 'Step 4 reached with null category — Step 2 must set category before reaching Step 4',
+ );
+
final categoryStr = switch (widget.model.category) {
ShopInBitCategory.concierge => "concierge",
ShopInBitCategory.travel => "travel",
ShopInBitCategory.car => "car",
- null => "concierge",
+ null => throw StateError('category must be non-null at Step 4 submit'),
};
final resp = await service.client.createRequest(
@@ -283,6 +288,11 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
+ final String descriptionPlaceholder =
+ widget.model.category == ShopInBitCategory.car
+ ? "Describe the car (make, model, year, requirements)"
+ : "What would you like to purchase?";
+
final content = Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -329,7 +339,7 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
: STextStyles.field(context),
decoration:
standardInputDecoration(
- "What would you like to purchase?",
+ descriptionPlaceholder,
_descriptionFocusNode,
context,
desktopMed: isDesktop,
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.