🏪

GuyStore

ASP.NET Core 9 MVC B2B e-commerce platform with SignalR, Identity auth, and iOS companion app

🚀 How to Run

# From the GuyStore\ folder
dotnet run --launch-profile http
# Starts at http://localhost:5090

Launch profiles (Properties/launchSettings.json):

ProfileURL
httphttp://localhost:5090
httpshttps://localhost:7091
IIS Expresshttp://localhost:5099

Runs in-memory by default ("UseInMemoryDatabase": true in appsettings.json). Set to false and supply a SQL Server connection string to persist data across restarts.

👤 Seeded Accounts

EmailPasswordRoles
[email protected]Admin@123Administrator, Registered
[email protected]Acct@123AccountManagement, Registered

📋 Features by Stage

Stage 1 Foundation

  • Public shop, product CRUD, cart, PO checkout, order management
  • ASP.NET Core Identity with approval workflow
  • 3 roles: Administrator, AccountManagement, Registered
  • One-way SignalR broadcast notifications
  • 2 themes: Corporate Blue, Dark Slate

Stage 2 Enhancements

  • Stock deduction on order placement with out-of-stock guard
  • Pagination on shop, admin products, admin orders, my orders
  • User self-service password change on profile page
  • Third theme: Forest Green + CSS variable customization panel (/Theme/Customize)
  • Email notifications via SMTP (account approval + order confirmation)
  • Two-way SignalR support chat: floating widget for non-admin users, admin Support Inbox
  • Invoice PDF generation using QuestPDF 2024.12 (/Cart/Invoice/{id}, admin order detail)
  • Two new roles: Viewer (browse-only, no cart), SalesRep (full shopping, no admin)
  • Role assignment modal in admin Accounts page
  • Product variants — admin can add size/color variants per product, each with its own stock quantity and price adjustment; variant picker shown on Shop Detail page
  • Product reviews — authenticated users can leave a 1–5 star review with title and body; one review per user per product; displayed on Shop Detail page with average rating
  • Discount/coupon codes — admin CRUD (/Admin/Discounts): code, percent off, max uses, expiry; users apply a code on the cart page; discount is deducted from order total
  • Shipping address management — users save multiple shipping addresses with a default; /Address CRUD pages; default address pre-fills the checkout billing form
  • Two-factor authentication (TOTP) — users set up an authenticator app via QR code (/Account/TwoFactor); 2FA code entry page enforced on next login; enable/disable toggle on profile
  • External OAuth login — Google and Microsoft sign-in buttons on the login page; accounts created automatically and linked to existing users
  • Admin reports (/Admin/Reports) — 30-day revenue trend chart, top 5 products by revenue, order status breakdown, month-to-date revenue vs previous month
  • Bulk CSV import (/Admin/Import) — admin uploads a CSV file to create multiple orders in one operation
  • Voice translation (/VoiceFeature/Translate) — speech input via Web Speech API in the user's chosen language, translated via MyMemory API (12 languages: en, zh-TW, zh-CN, ja, ko, fr, de, es, it, pt, vi, th), translated text read back via speech synthesis; translation history (last 10 entries)

Stage 3 Screen Capture Feedback

  • Screen Capture Feedback under Voice menu (authenticated non-admin users)
  • Capture screen via getDisplayMedia() or upload an image, add a message, submit to admin
  • ScreenFeedback model (message, image binary, status, admin reply) + EF migration
  • ScreenFeedbackHub at /screenFeedbackHub — real-time admin notification + user reply delivery
  • Admin Feedback Inbox (/Admin/Feedback) — list, filter, view screenshot, reply
  • Sidebar live badge counts incoming feedback in real time
  • MyFeedback page shows submission history and admin replies

Stage 4 Crop + Draw + Mobile API + Payment Methods

  • Rectangular crop selection on Screen Capture page — drag to draw region, dim overlay with dashed border, corner handles, pixel-size label
  • Freehand drawing / annotation on Screen Capture page — toggle between ✂️ Crop and ✏️ Draw mode; 7 color swatches, stroke-width slider (2–24 px), Undo (+ Ctrl+Z), Clear
  • Single-canvas design (#mainCanvas) — image, strokes, and crop overlay all rendered together, eliminating dual-canvas alignment bugs
  • Strokes stored in JS memory; baked into the submitted image at full resolution only on form submit (no per-stroke re-bake overhead)
  • JSON REST API at /api for the VoiceSmart iOS companion app (cookie-based auth, no AntiForgery)
  • Configurable payment methods — admin CRUD (/Admin/PaymentMethods): name, code, PO# required flag, sort order, enable/disable, set default
  • AppSetting key-value store — admin settings page (/Admin/Settings); EmailOrderConfirmation toggle controls whether order emails are sent
  • Checkout page shows payment method radio buttons; PO# field shown/hidden dynamically by JavaScript based on selected method
  • Order confirmation email skipped when EmailOrderConfirmation is disabled
  • New mobile API endpoints: GET /api/products, GET /api/payment-methods, POST /api/orders
  • VoiceSmart MAUI app gains Shop tab: product list, in-memory cart, checkout with payment method picker and conditional PO# field
  • VoiceSmart MAUI app adds Draw annotation to Feedback tab — freehand drawing on captured/selected images before submitting
  • VoiceSmart MAUI checkout supports coupon code entryPOST /api/orders now accepts couponCode?, validates against DiscountCodes table, deducts discount from total

Stage 5 Pagination + Seed Data

  • Admin panel pagination extended to all list pagesPaginatedList<T> gained a Create() overload for pre-built lists; four controllers newly paginated:
    • Accounts (15/page) — pending count passed via ViewBag so it reflects all users, not just the current page
    • Discounts (20/page)
    • Feedback Inbox (20/page) — status filter preserved across pages by _Pagination.cshtml's query-string passthrough
    • Payment Methods (25/page) — controls hidden when ≤ 1 page
  • Paginated mobile APIGET /api/products and GET /api/feedback/mine now accept ?page=&pageSize= and return {page, pageSize, total, totalPages, items:[…]}
  • VoiceSmart Load More — Shop (20/page) and My Feedback (10/page) pages load page 1 on open; CollectionView.Footer shows a "Load More" button when HasMore is true
  • Extended seed dataDataSeeder.cs expanded to 30 products (15 Electronics, 8 Office, 7 Accessories) and 21 discount codes
  • SQL Server migration 20260517041218_SeedPaginationTestData — idempotent IF NOT EXISTS … INSERT statements apply the same 22 new products and 21 discount codes to GuyStoreDb

Stage 6 Pagination Test Suites

  • GuyStore unit testsPaginatedListTests.cs extended with 4 tests for the synchronous Create() overload (93 unit tests total)
  • GuyStore integration tests — new AdminPaginationTests.cs (12 tests: Accounts, Discounts, Feedback, PaymentMethods pagination); ApiControllerTests.cs updated to assert paginated-envelope shape + pageSize clamping (139 integration tests total)
  • GuyStore E2E tests — new PaginationTests.cs (6 browser tests: Shop + Admin Products + Admin Discounts page navigation)
  • VoiceSmart.Tests — new net9.0 xUnit project at iPhoneApp\VoiceSmart.Tests\; links source files from the iOS MAUI project directly; MainThread stub satisfies the one MAUI platform API; 19 ViewModel tests covering ShopViewModel and MyFeedbackViewModel pagination + SignalR reply logic

Stage 7 Passkeys (WebAuthn / FIDO2)

  • Password-free sign-in using Face ID, Touch ID, Windows Hello, or a hardware security key — no password entry required
  • Register passkeys from Profile → Passkeys (GET /Passkey/Manage): give each passkey a friendly name, view creation and last-used dates, remove individual passkeys
  • "Sign in with Passkey" button on the Login page uses the discoverable/resident credential flow — no email entry needed; the browser presents an account picker automatically
  • Multiple passkeys per account supported
  • PasskeyCredentials table stores credential ID (base64url string), COSE public key, user handle, and rolling signature counter per credential; uses existing PasskeyCredential EF entity
  • Server-side: PasskeyService wraps IFido2 from the Fido2.AspNet v4.0.1 NuGet package; registration and authentication challenges are stored in ASP.NET Core session between Begin and Complete calls
  • PasskeyController exposes 5 endpoints: BeginRegistration, CompleteRegistration, BeginAuthentication, CompleteAuthentication, Remove
  • Client-side: wwwroot/js/passkey.jsregisterPasskey() and signInWithPasskey() with base64url encode/decode helpers; no external JS library needed
  • Existing IsApproved workflow is preserved — unapproved accounts cannot sign in via passkey either
  • Configure RP domain and allowed origins in appsettings.json under the "Fido2" section (defaults to localhost); update ServerDomain and Origins when deploying to a real domain
  • Database toggle: "UseInMemoryDatabase": false in appsettings.json switches EF Core from InMemory to SQL Server; run dotnet ef database update to apply the AddPasskeyCredentials migration

📱 Mobile JSON API

Route prefix: /api — cookie-based Identity auth, no AntiForgery tokens. Client logs in via POST /api/auth/login, receives a session cookie, and includes it in all subsequent requests via CookieContainer.

MethodEndpointAuthDescription
POST /api/auth/login {email, password}{success, userName, …} + sets session cookie
POST /api/auth/logout Required Signs out
GET /api/auth/me {authenticated, userName}
POST /api/feedback Required Multipart: message, pageUrl, image? (≤8 MB, JPEG/PNG/GIF/WebP) — pushes SignalR on receipt
GET /api/feedback/mine?page=1&pageSize=10 Required User's own feedback as paginated envelope {page, pageSize, total, totalPages, items:[…]} (pageSize clamped 1–50)
GET /api/products?page=1&pageSize=20 Required Published products as paginated envelope {page, pageSize, total, totalPages, items:[…]} (pageSize clamped 1–100)
GET /api/payment-methods Required All active payment methods as JSON array
POST /api/orders Required {paymentMethodId, poNumber?, couponCode?, billingCompany, billingAddress, billingCity, billingState, billingZip, contactName, contactPhone, notes?, items:[{productId,quantity}]}{orderId, orderNumber, total, discountAmount, status}

🎙️ Voice Features

All voice pages are under /VoiceFeature/ and are accessible via the Voice nav dropdown (authenticated users only).

PageRouteDescription
Voice Camera /VoiceFeature/Camera Say a trigger word (language-aware) to capture the webcam. Supports English, Traditional Chinese, Simplified Chinese, Taiwanese Hokkien.
Voice Translation /VoiceFeature/Translate Speak in one language, see and hear the translation. 12 languages: en, zh-TW, zh-CN, ja, ko, fr, de, es, it, pt, vi, th. Powered by MyMemory API (free tier). Translation history (last 10).
Screen Capture /VoiceFeature/ScreenCapture Capture a screenshot or upload an image, crop or annotate with freehand drawing, add a message, and submit as feedback to the admin.
My Feedback /VoiceFeature/MyFeedback View all feedback submissions and admin replies. Replies delivered in real time via SignalR.

Voice camera trigger words: voice-camera.jsTRIGGERS map keyed by BCP-47 language code. Taiwanese Hokkien uses zh-TW recognition with a reduced word list (only words with Mandarin-compatible phonetics).

⚡ SignalR Hubs

HubRouteGroupsEvents
NotificationHub /notificationHub all clients ReceiveNotification
SupportHub /supportHub admins, user_{userId} ReceiveMessage, ReceiveAdminReply
ScreenFeedbackHub /screenFeedbackHub sfb_admins, sfb_{userId} NewFeedback, ReceiveFeedbackReply

🔧 Known Bug Fixes

Fix A — Stage 3
Chrome Tab capture produced blank/black image
Root cause: ImageCapture.grabFrame() returns black frames for display-media streams. Tracks were also stopped in a shared finally before the video rendered a frame.
Fix: Removed ImageCapture. Stream piped into a <video>; requestVideoFrameCallback guarantees first real frame before drawImage(). Tracks stopped only after drawing.
Fix B — Stage 3
User never received admin reply in real time
Root cause: SignalR hub connection lived only inside ScreenCapture.cshtml. Redirect to MyFeedback destroyed the page and its socket.
Fix: Created wwwroot/js/screen-feedback.js — persistent hub loaded globally in _Layout.cshtml. On reply, dispatches CustomEvent("sfbReply") that any page can listen for.
Fix C — Stage 3
IIS port 80: redirect URLs contained internal Kestrel port 5090
Root cause: IIS proxied to Kestrel; Request.Host was localhost:5090 so RedirectToAction produced unreachable URLs.
Fix 1: UseForwardedHeaders added as first middleware — rewrites Request.Host from X-Forwarded-Host.
Fix 2: web.config with hostingModel="inprocess" — IIS loads app directly; no separate Kestrel port.
Fix D — Stage 4
Crop rectangle not drawn; Chrome Tab capture showed nothing
Root cause 1 (crop): Dual-canvas design — displayCanvas centered by flex, overlayCanvas centered by position:absolute. Browser rounding causes sub-pixel misalignment; drag events land in the wrong position.
Root cause 2 (capture): <video> not in the DOM; Chrome does not reliably deliver display-media frames. videoWidth can be 0 on the first requestVideoFrameCallback.
Fix: Replaced dual canvas with single #mainCanvas; image and overlay drawn together in drawAll(). Video appended to document.body (hidden) during capture; polls videoWidth > 0 up to 3 s before drawing.

🌐 IIS Deployment

📲 Companion App

VoiceSmart iOS MAUI app is located at:

C:\Guy\Notes\Claud Projects\iPhoneApp\VoiceSmart\

See VoiceSmart\README.html for setup, features, and API details.

GuyStore Project Documentation — Last updated 2026-06-12 (Stage 7: Passkeys + UseInMemoryDatabase toggle)