Implemented responsive mobile card layouts for all main Index views, providing superior mobile UX while maintaining desktop table views.
**Responsive Design Pattern:**
- Desktop (≥992px): Table layout with all data columns
- Mobile (<992px): Card-based layout optimized for touch interaction
- Breakpoint: Bootstrap's lg breakpoint for optimal viewing experience
**Views Converted:**
1. **Categories/Index.cshtml:**
- Mobile cards with name, description, product count, status
- Full-width action buttons for easy touch interaction
- Clear visual hierarchy with icons and badges
2. **Users/Index.cshtml:**
- Simplified mobile cards showing username, created date, status
- Conditional delete button (protected admin account)
- Clean, minimal design for quick user management
3. **ShippingRates/Index.cshtml:**
- 2x2 grid layout for shipping rate data (country, price, weight, delivery)
- Visual separation with light background boxes
- All critical information displayed in scannable format
4. **VariantCollections/Index.cshtml:**
- Properties JSON displayed in scrollable code block
- Created/Updated dates in compact format
- Clear deactivation action for variant collections
**Mobile UX Enhancements:**
- ✅ 44px minimum touch targets (Bootstrap .btn default)
- ✅ Full-width buttons with .d-grid gap-2 for easy tapping
- ✅ Proper spacing with mb-3 between cards
- ✅ Clear visual hierarchy with card-title and badges
- ✅ Descriptive button text (not just icons) on mobile
- ✅ Responsive icons and status indicators
- ✅ Word-break handling for long JSON strings
**Technical Implementation:**
- Used Bootstrap's d-none d-lg-block for desktop tables
- Used d-lg-none for mobile card views
- No JavaScript required - pure CSS responsive design
- Maintains all functionality from desktop view
- Zero data loss in mobile transformation
**Accessibility Maintained:**
- All ARIA labels preserved from Phase 1
- Semantic HTML structure in both views
- Proper heading hierarchy maintained
- Keyboard navigation fully functional
🚀 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented comprehensive accessibility enhancements to meet WCAG 2.1 AA standards:
**Skip Navigation:**
- Added skip-to-content link for keyboard users
- Link appears on focus and jumps directly to main content area
**Screen Reader Support:**
- Created .sr-only and .sr-only-focusable utility classes
- Added aria-hidden="true" to all decorative icons
- Added descriptive aria-label attributes to all icon-only buttons
**Enhanced Focus Indicators:**
- Implemented 3px visible outlines on all interactive elements
- Added :focus-visible for keyboard-only focus indicators
- Special focus styling for primary actions (orange outline)
- Consistent 2px outline-offset for better visibility
**Table Accessibility:**
- Added scope="col" attributes to all table headers
- Properly grouped button actions with role="group" and aria-label
**Button Improvements:**
- All icon-only buttons now have descriptive ARIA labels
- Added responsive text labels (visible on sm+ screens, hidden on mobile)
- Improved button groups with proper ARIA roles
**Files Modified:**
- _Layout.cshtml: Skip link, accessible menu close button
- Categories/Index.cshtml: ARIA labels, table scopes
- Users/Index.cshtml: ARIA labels, table scopes
- Orders/Index.cshtml: Table scopes
- Products/Index.cshtml: Table scopes
- ShippingRates/Index.cshtml: ARIA labels, table scopes
- VariantCollections/Index.cshtml: ARIA labels, table scopes
- modern-admin.css: Accessibility utilities and enhanced focus styles
**WCAG 2.1 AA Criteria Addressed:**
- 2.4.1 Bypass Blocks (Level A)
- 2.4.7 Focus Visible (Level AA)
- 4.1.2 Name, Role, Value (Level A)
- 1.3.1 Info and Relationships (Level A)
🚀 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed JSON naming policy from CamelCase to SnakeCaseLower for SilverPay API compatibility
- Fixed field name from 'fiat_amount' to 'amount' in request body
- Used unique payment ID instead of order ID to avoid duplicate external_id conflicts
- Modified SilverPayApiResponse to handle string amounts from API
- Added [JsonIgnore] attributes to computed properties to prevent JSON serialization conflicts
- Fixed test compilation errors (mock service and enum casting issues)
- Updated SilverPay endpoint to http://10.0.0.52:8001/🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for £0 order bug:
- When users select a variant and click 'Add to Basket', the confirmvar: callback triggers HandleConfirmVariant
- This method was passing variantId: null to AddItem(), causing cart items to have no variant and price £0
- Now looks up selected variant by name, extracts its ID, and passes it to cart
- Added logging to track which variant is being used
- Also includes CSV variant conversion utility and sample fixed import file
Previous build at 20:05 used cached layers from 18:06.
This empty commit will trigger pipeline #519 to rebuild fresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CRITICAL BUG FIX: HandleAddToCart was only checking MultiBuys for price,
never ProductVariants. This caused all variant-based products to use the
base price (£0), resulting in £0 orders.
Changes:
- HandleAddToCart now checks variants FIRST for pricing
- Falls back to multi-buy, then base price (correct priority order)
- Uses proper Product-based AddItem() method to pass variant IDs
- Added logging to track which pricing method is used
- HandleQuickBuy already had correct variant detection (no changes)
Result: Orders now correctly calculate total using variant prices (e.g., £90, £160)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Log variant count and prices when fetching products to diagnose
why variants aren't being detected during add-to-cart flow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added logging to diagnose why orders are created with £0 pricing:
- Log when product has variants and variant selection is shown
- Log WARNING when product has no variants and base price is used
- Helps identify if variants are missing or not being detected
Troubleshooting: Orders showing £0 despite variants having correct prices
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CRITICAL BUG FIX: Orders were totaling £0 because imported products had:
- Base price set to £0
- Variants stored as ProductMultiBuys (not ProductVariants)
- Order creation couldn't find variant prices → used base price of £0
Changes:
- CSV import now detects format: semicolons (;) = variants, colons (:) = multi-buys
- Added ImportProductVariantsAsync() to handle pipe-delimited variant format
- Format: "value; price; stock|value; price; stock" (e.g., "10; 30.00; 100|25; 70.00; 50")
- Updated CSV export to prioritize ProductVariants over MultiBuys
- Updated CSV template with correct variant format examples
- Added comprehensive documentation in VARIANT_CSV_FORMAT.md
Migration Required:
- Existing products with incorrect MultiBuy data need re-import
- Convert format from "name:qty:price;name:qty:price" to "value; price; stock|value; price; stock"
- Ensure base price is 0 for variant-based products
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Previous Approach (WRONG):**
- Created a separate table section to display ProductVariants
- User wanted data in the EXISTING collapsible panels, not a new section
**Proper Fix:**
- ProductImportService creates records in ProductVariants table
- Edit page's collapsible panels read from VariantsJson field (different system)
- Solution: Convert ProductVariants → VariantsJson format on page load
**Changes:**
1. **ProductsController.cs (Lines 105-115):**
- Load ProductVariants from database
- If VariantsJson empty but ProductVariants exist, convert them
- Format: `[{Weight: "28g", Price: 700, StockQty: 100}, ...]`
- JavaScript reads Price and StockQty to populate collapsible panel fields
2. **Edit.cshtml:**
- Removed the extra table section
- Existing collapsible panels now display imported data automatically
**Result:**
When you open Edit page, expand "Product Variants" → "Price, Stock & Weight Details",
the fields will be PRE-FILLED with your imported values (700, 100, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
- Variant price overrides and stock quantities were hidden in collapsible
panels in the Product Edit page
- The Edit page was showing the VariantCollections system (VariantsJson)
instead of the actual ProductVariant records created by text import
- User had to expand each variant panel to see price and stock values
**Solution:**
1. **ProductsController.cs (Lines 101-103):**
- Added call to GetProductVariantsAsync() to load actual variant records
- Added ViewData["ProductVariants"] to pass data to view
2. **Edit.cshtml (Lines 320-413):**
- Added new collapsible section "Product Variants"
- Displays variants in a table with directly visible columns:
* Name, Type, Price, Stock Level, Sort Order, Status
- No hidden panels - all information visible at a glance
- Added quick summary with total variants, stock, and price range
- Includes helpful links to ProductVariants management page
**Technical Details:**
- Price displays in green with £ symbol when override exists
- Stock shows color-coded badges (green=in stock, red=out of stock)
- Section only appears if variants exist (conditional rendering)
- Expanded by default (aria-expanded="true") for immediate visibility
**Impact:**
- User can now see all variant prices and stock quantities immediately
- No need to click/expand individual variant panels
- Better UX for products imported via text import format
- Maintains separation between VariantCollections system and ProductVariants
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added detailed logging to ParseAndImportVariantLine to output:
- Variant name/value
- Price override
- Stock quantity
- Variant type
This will help diagnose why variant price and stock aren't showing up
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add replaceAll checkbox to ImportText view with warning
- Pass replaceAll parameter to import service
- Allows complete product catalog replacement via text import
- Enhanced documentation for unit types and variant collections
- Fix optimistic concurrency errors in product import by using ExecuteSqlRaw instead of EF tracking
- Add DeleteAllOrdersAndSalesDataAsync() method to clear orders, payments, customers, and messages
- Add DeleteAllSalesData endpoint to ProductsController for admin access
- Proper deletion order to avoid foreign key violations
- Enhanced logging for troubleshooting data cleanup operations
Resolves: Import errors with 'database operation expected to affect 1 row(s)'
- Added Replace All checkbox to import UI for clean slate imports
- Implemented DeleteAllProductsAndCategoriesAsync for complete data wipe
- Added auto-creation of categories during CSV import
- Created products_import.csv with 13 products across 4 categories
- Added comprehensive IMPORT_INSTRUCTIONS.md documentation
Technical changes:
- ProductImportService: Added replaceAll parameter to all import methods
- ProductImportService: Categories now auto-created if missing from CSV
- ProductsController: Added replaceAll parameter to Import action
- Import.cshtml: Added Replace All checkbox with danger warnings
Categories: Flour, Cereal, Vitamins, Herbal
Products: 13 products with full variant pricing structures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created NotificationHub for instant browser notifications
- Updated CryptoPaymentService to broadcast via SignalR
- Added JavaScript client with toast notifications
- Works with custom SSL certificates (no FCM dependency)
- Automatic reconnection with exponential backoff
- Notification sound and visual indicators
- Bypasses all Web Push SSL certificate issues
TeleBot Configuration:
- Added TeleBot API URL and API key to docker-compose.yml
- Configured to connect to telebot-service:5000 internally
- Enables customer notifications via Telegram bot
Expired Payment Handling:
- Auto-cancel orders when payment status is Expired
- Only cancels orders in PendingPayment status
- Logs cancellation for audit trail
Customer View Improvements:
- Hide cancelled orders from customer order lists
- Filters applied to both GetOrdersByIdentityAsync and GetOrdersByCustomerIdAsync
- Prevents confusion from displaying cancelled/expired orders
This resolves:
- No notifications to customers (TeleBot not configured)
- No notifications to admin (TeleBot connection failed)
- Expired orders remaining visible to customers
- Orders not auto-cancelled when payment expires
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Webhook Improvements:
- Added Confirmations field to PaymentWebhookDto (default: 0)
- Updated webhook controller to pass confirmations to service layer
- Fixed notification logic to match order update conditions
Payment Confirmation Logic:
- Paid (2): Confirmed immediately regardless of confirmations
- Overpaid (3): Confirmed immediately regardless of confirmations
- Completed (7): Requires 3+ blockchain confirmations
- Notifications only sent when order is actually updated
This prevents premature notifications for unconfirmed 'Completed' status
while maintaining immediate processing for 'Paid' and 'Overpaid' statuses.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added quick action button on Packing tab to dispatch orders
- Created dispatch modal with tracking number input
- Modal includes tracking number, estimated days, and notes fields
- Button appears on both desktop table and mobile card views
- Fixes workflow gap where Packing orders had no quick action
- Orders now properly flow: Accepted → Packing → Dispatched
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added PaymentStatus.Overpaid to notification trigger conditions
- Overpaid payments now update order status to PaymentReceived
- Overpaid payments now send admin push notifications
- Overpaid payments now send TeleBot customer notifications
- Resolves issue where successful overpayments were silent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed Issues:
1. Removed "maskable" purpose from icons (was causing padding warnings)
2. Added screenshots with form_factor for richer install UI
- Desktop: form_factor: "wide"
- Mobile: form_factor: "narrow"
Changes:
- Icons now use "purpose: any" only (no maskable)
- Added screenshots array with wide/narrow form factors
- This enables richer PWA install prompts on supported browsers
All PWA manifest warnings should now be resolved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problems Fixed:
1. Blank white screen on initial load (loading screen had display:none)
2. Only showed once (sessionStorage.blazorLoaded prevented repeat shows)
3. Fast connections meant users never saw it
Solution:
1. Removed display:none from HTML - screen visible immediately
2. Removed sessionStorage check - shows on every page load
3. Screen visible by default, hides when Blazor.start() completes
Behavior Now:
- Loading screen appears instantly (no blank white screen)
- Shows on every page load (full page refresh)
- Hides when SignalR connection established
- Works correctly with slow/throttled connections
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Loading screen was hiding immediately without waiting for Blazor
- Page detection logic was too restrictive (only /blazor paths)
- Most admin pages don't have Blazor components, so screen hid instantly
Solution:
- Blazor Server is loaded on ALL admin pages via _Layout.cshtml
- Removed restrictive path checking (was checking for /blazor or components)
- Now always calls Blazor.start() and waits for SignalR connection
- Loading screen properly shows while SignalR establishes connection
Expected behavior:
- First load: Screen shows → Blazor connects → Screen fades out
- Console: "Starting Blazor Server..." → "Started successfully" → "Hiding"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**:
- GetAvailableCurrenciesAsync() was routing internal API calls through Tor
- Caused SOCKS connection failures: "SOCKS server failed to connect"
- Internal Docker network calls don't need Tor privacy layer
**Root Cause**:
- Line 617-618 used OR logic: LittleShop:UseTor OR Privacy:EnableTor
- Production config: LittleShop__UseTor=false, Privacy__EnableTor=true
- OR condition meant Tor was enabled for all API calls
**Solution**:
- Only check LittleShop:UseTor (explicitly for API calls)
- Privacy:EnableTor now only affects external Telegram API calls
- Internal calls (http://littleshop:5000) bypass Tor completely
**Benefits**:
- No more SOCKS connection errors
- Faster internal API responses (no Tor overhead)
- Tor still protects external Telegram communications
**File**: TeleBot/TeleBot/Services/LittleShopService.cs:619
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Ephemeral sessions weren't persisted after checkout
- Sessions ephemeral by default, only persisted during CheckoutFlow
- After checkout completes, state changes to MainMenu
- SavedAddress was lost because session wasn't saved
Fix implemented:
- Persist ephemeral sessions when SavedAddress is not null
- Also persist when cart is non-empty (preserve cart state)
- User's saved shipping address now available on next checkout
- Improves UX - no need to re-enter address every time
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Orders created with CustomerInfo had NULL IdentityReference
- CancelOrderAsync checked order.IdentityReference != identityReference
- NULL != "telegram:12345:username" → always returned false
- User saw "already processed" error even for pending orders
Fix implemented:
- Include Customer entity in CancelOrderAsync query
- Extract Telegram user ID from identity reference format
- Match against Customer.TelegramUserId for modern orders
- Fallback to IdentityReference matching for legacy orders
- Enhanced logging to debug ownership/status issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added delete order functionality with identity verification
- OrderDetailsMenu now shows conditional buttons based on order/payment state
- Retry payment if no payments exist
- View payment details if payment pending
- Delete order option for all pending orders
- Full client SDK implementation for CancelOrderAsync
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: When clicking Buy button, product details message appeared at bottom
but Telegram didn't auto-scroll to show it, leaving users confused.
Solution: Use replyToMessageId parameter to thread product details as a reply
to the user's button click message. This:
- Creates visual thread connection
- Encourages Telegram to show the response prominently
- Makes conversation flow more natural
Also wrapped activity tracking in try-catch to prevent blocking.
Technical Changes:
- ProductCarouselService: Added replyToMessageId optional parameter
- SendPhotoAsync/SendTextMessageAsync: Use replyToMessageId with allowSendingWithoutReply
- HandleProductDetail: Pass message.MessageId to carousel service
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Error: MenuBuilder.PaymentCurrencyMenu does not exist
Fix: Changed to MenuBuilder.PaymentMethodMenu (correct method name)
TeleBot now compiles successfully.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Activity tracker DNS errors (littleshop-admin:8080) were blocking
checkout flow, preventing shipping address prompt from showing.
Solution: Wrap activity tracking in try-catch with warning log.
Checkout flow continues even if activity tracking fails.
This ensures users can complete checkout even if analytics are unavailable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Remove /help from main menu
- Removed "❓ Help" button from main menu
- Help still available via /help command
2. Fix support refresh clearing messages
- Changed from editing message to sending new message
- Preserves message history when refreshing
- Shows "No new messages" if no updates instead of clearing
- Better error handling with user-friendly alerts
3. Add retry payment button for failed payments
- New "🔄 Retry Payment" button when payment creation fails
- Shows order ID (friendly reference) in error message
- PaymentFailedMenu: Retry, View Basket, Main Menu options
- HandleRetryPayment: Re-shows currency selection
- Preserves order and allows payment retry without recreating order
Technical Changes:
- MenuBuilder: Added PaymentFailedMenu method
- CallbackHandler: Added retry_payment case and HandleRetryPayment method
- HandlePayment: Updated error messages to use PaymentFailedMenu
- HandleRefreshConversation: Sends new message instead of editing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Basket Summary Below Navigation
- Welcome message now shows basket items and total
- Format: "🛒 Basket: X item(s) - £X.XX"
- Only shown when basket has items
2. Order Display Improvements
- Order GUID replaced with friendly reference (last 8 chars: #XXXXXXXX)
- Added "View Payment Details" button for pending payment orders
- Button shows QR code, amount, address, and time until expiry
- Variants now properly displayed in order items (already working via commit 330116e)
3. Payment Details View (New Feature)
- HandleViewPayment: Shows payment info with QR code
- Displays: Currency, amount, address, expiry time
- Shows time remaining until payment expires
- QR code generation for easy mobile payment
- OrderDetailsMenu: Context-aware navigation buttons
4. Postal Address Auto-Load (Verified Working)
- Checkout automatically detects saved addresses
- Offers to use saved address with preview
- One-click selection or option to enter new address
- SavedAddress copied to OrderFlow when selected
Technical Changes:
- MessageFormatter.FormatWelcome: Added optional cart parameter
- MessageFormatter.FormatOrder: Uses friendly order reference
- MenuBuilder.OrderDetailsMenu: New menu with payment button
- CallbackHandler.HandleViewPayment: Payment details with QR
- CallbackHandler.HandleMainMenu: Pass cart to welcome formatter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1. Show variants immediately on product page (removed duplicate buy button)
- Variants now display directly on product details page
- No intermediate "select variant" button needed
- Faster checkout flow
2. Add post-purchase prompt (Checkout or Continue Shopping)
- After adding to basket, shows "What would you like to do next?"
- Options: View Basket, Checkout, or Continue Shopping
- Continue Shopping returns to the product page
3. Remove quantity +/- buttons from product details
- Simplified to single item purchase only
- Cleaner interface for mobile users
4. Rename "Cart" to "Basket" throughout
- All user-facing text updated
- "Add to Cart" → "Add to Basket"
- "Shopping Cart" → "Shopping Basket"
- "View Cart" → "View Basket"
- "Clear Cart" → "Clear Basket"
Technical Changes:
- MenuBuilder.ProductDetailMenu: Now shows variant selection inline
- CallbackHandler: Updated to use ProductDetailMenu for variant updates
- Added PostAddToCartMenu for post-purchase options
- HandleAddToCart/HandleConfirmVariant: Show prompt instead of returning to product
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Selecting Size then Color would reset Size selection
- Users could never get "Add to Cart" button with multiple variant types
- Each selection created a NEW list, wiping previous choices
Root Cause:
- HandleSetVariant created: new List<string> { variantName }
- This replaced all previous selections instead of accumulating
Fix:
1. Get existing selected variants from session
2. Find the variant type of newly selected variant
3. Remove any previous selection from the SAME type (allow changing choice)
4. Add the new selection
5. Save accumulated list back to session
Example behavior now:
- Select "Red" (Color) → selectedVariants = ["Red"]
- Select "Large" (Size) → selectedVariants = ["Red", "Large"] ✅
- Select "Blue" (Color) → selectedVariants = ["Blue", "Large"] ✅
- Can now confirm when all types selected
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>