POST /api/v1/storefront/auth/forgot-password and .../reset-password were
both declared with bare `email: str` / `reset_token: str, new_password: str`
parameters. FastAPI treats unannotated str params as query parameters, so
the frontend's JSON body was ignored and the endpoint 422'd with
"missing query parameter 'email'". The docstrings on both endpoints
already said "Request Body" — intent was clear, implementation drifted.
Add two new Pydantic body schemas in tenancy/schemas/auth.py:
PasswordResetRequest { email: str } (forgot)
PasswordResetConfirm { reset_token: str, new_password: str } (reset)
Re-export from tenancy/schemas/__init__.py, import in
customers/routes/api/storefront.py, and switch both endpoint signatures
to take `body: <Schema>`. Internal usage reads body.email / body.reset_token
/ body.new_password.
Surfaced during Test 5 when user clicked "forgot password" on the customer
storefront login page to set a password for the first time after a
self-enrollment flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The initial codemod only converted url_for('*_static', path='*.js'|'*.css')
patterns and missed 19 raw /static/... references — most importantly the
shared/fonts/inter.css link in all four base.html files, plus a handful
of <script src="/static/modules/..."> tags in marketplace/billing/orders
templates and the storefront login/register/forgot/reset pages.
Result: deploys now flip ?v=<sha> on every JS/CSS asset that reaches the
browser, not just the ones loaded via url_for().
FE-024 rule extended to flag src="/static/...*.(js|css)" patterns too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `static_v(request, name, path=...)` Jinja helper that appends
?v=<commit-sha> from app.core.build_info, plus a CachedStaticFiles
subclass that serves Cache-Control: public, max-age=31536000, immutable
in production and no-cache in development. Browsers refetch JS/CSS
automatically on every deploy without the user having to hard-reload.
- New: app/core/static_files.py (CachedStaticFiles)
- Updated: app/templates_config.py (static_v helper)
- Updated: main.py (use CachedStaticFiles for *_static mounts)
- Codemod: 143 url_for('*_static', path='*.js'|'*.css') → static_v(...)
across 123 templates. Images/fonts/JSON locales intentionally
unchanged (out of scope).
- Arch rule: FE-024 (warning) flags raw url_for on JS/CSS to prevent
drift. Note: FE-008 was already taken by the number_stepper rule.
- docs/proposals/static-asset-cache-busting.md marked Done.
Closes plan from docs/proposals/static-asset-cache-busting.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small storefront i18n improvements found during the FR
pre-launch walkthrough on FASHIONHUB:
- Store description (e.g. "Trendy clothing and accessories") was a
single English string rendering in the footer regardless of locale.
Added a description_translations JSON column on Store with the same
shape used elsewhere (CMS, Platform, Subscription), exposed via
get_translated_description(lang), and updated the footer + meta tag
to use it. Seeded FR/DE/LB/EN for FASHIONHUB and FASHIONOUTLET so
Fashion Group renders correctly out of the box. Other stores still
show the single description field as fallback.
- "Home" was a hardcoded English literal in both desktop and mobile
nav, even though the FR translation already existed at nav.home in
static/locales/fr.json. Now uses _('nav.home').
- <html lang="en"> was hardcoded, which made <input type="date"> show
in mm/dd/yyyy on the FR storefront. Now driven by current_language
so the browser's locale-aware date picker matches the page locale.
Migration tenancy_005 adds the description_translations column;
nullable, no backfill needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared loyalty pins list factory has an autocomplete-from-team
flow gated behind config.staffApiPrefix (loaded once into a list, then
filtered client-side). The merchant entry in static/merchant/js/loyalty-pins.js
never set staffApiPrefix, so the loadStaffMembers branch never ran and
the "Staff member name" field on /merchants/loyalty/pins fell back to
free text instead of suggesting actual team members.
Wire the merchant config to /merchants/account, and add a flat
GET /merchants/account/team/members alias next to the existing
/merchants/account/team that returns just {members: [...]} — matching
the shape the shared autocomplete factory already expects at
${prefix}/team/members.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the backend half of the Android tablet rollout. Merchants can
pair tablets to specific stores from /merchants/loyalty/devices (or
admins can pair on behalf from the merchant detail page). Each
pairing issues a long-lived JWT shown ONCE in the response with a
server-rendered QR PNG containing {api_url, store_code, auth_token} —
the tablet scans it on first boot and persists the three fields.
The store API (/api/v1/store/loyalty/*) now accepts these device JWTs
alongside user JWTs. Revoking a device row immediately rejects its
token (401 TERMINAL_DEVICE_REVOKED). Tokens expire after 1 year;
re-pair to renew.
- Migration loyalty_010 + TerminalDevice model
- create_device_token / verify_device_token JWT helpers
- 5 endpoints x 2 portals (merchant + admin on-behalf)
- Bearer-auth wiring in app/api/deps.py
- Pages, shared list partial with one-time pairing-QR modal,
Alpine.js factories
- Locale strings (en authoritative; fr/de/lb seeded with EN copy
for translation)
- 6 integration tests covering pair, list, revoke, idempotency,
cross-merchant rejection, store-API auth via device JWT
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move store theme admin pages, templates, and JS from tenancy module
to CMS module where the data layer (model, service, API, schemas)
already lives. Eliminates split ownership.
Moved:
- Route handlers: GET /store-themes, GET /stores/{code}/theme
- Templates: store-theme.html, store-themes.html
- JS: store-theme.js, store-themes.js
- Updated static references: tenancy_static → cms_static
Deleted old tenancy files (no remaining references).
Menu item in CMS definition already pointed to correct route.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Table actions now show view + edit + delete (trash icon) for non-owner
members. Delete opens the existing remove-from-all-stores modal.
Edit modal enhanced with "Add to another store" section:
- Shows a dashed-border card with store dropdown + role dropdown + add button
- Only appears when the member is not yet in all merchant stores
- Uses the existing invite API to add the member to the selected store
i18n: 2 new keys (add_to_store, select_store) in 4 locales.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix contact_type column: Enum(ContactType) → String(20) to match the
migration (fixes "type contacttype does not exist" on insert)
- Rewrite scrape_contacts with structured-first approach:
Phase 1: tel:/mailto: href extraction (high confidence)
Phase 2: regex fallback with SVG/script stripping, international phone
pattern (requires + prefix, min 10 digits)
Phase 3: address extraction from Schema.org JSON-LD, <address> tags,
and European street address regex (FR/DE/EN street keywords)
- URL-decode email values, strip tags to plain text for cross-element
address matching
- Add /mentions-legales to scanned paths
Tested on batirenovation-strasbourg.fr: finds 3 contacts (email, phone,
address) vs 120+ false positives and a crash before.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The role dropdown was hidden for pending stores (x-show="!store.is_pending").
Pending members already have an assigned role that should be changeable
before acceptance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts the expandable sub-row design back to a clean one-row-per-member
table. All per-store management now happens inside the edit modal.
Table: simple 4-column layout (Member | Stores & Roles | Status | Actions)
with view + edit buttons. Store badges show orange for pending stores.
Edit modal enhanced with per-store cards showing:
- Store name, code, and status badge (Active/Pending)
- Role dropdown + Update button (for active stores)
- Resend invitation button (for pending stores)
- Remove from store button
- "Remove from all stores" link at bottom
Removed: expandedMembers, flattenedRows, toggleMemberExpand,
resendStoreInvitation, resendInvitation (member-level).
Added: resendForStore, removeFromStore (work inside edit modal).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The nested tbody approach caused browsers to collapse all cells into
one column. Replaced with a single flat x-for loop over flattenedRows
(computed property that interleaves member rows and store sub-rows).
Each row is a single <tr> with 4 proper <td> cells, using x-if to
conditionally render member-level or store-level content per column.
Sub-rows are hidden/shown via expandedMembers array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fixed header/column alignment: Member | Role | Status | Actions
- Store count + chevron moved inline with member name (not a separate column)
- Role column shows single role, "Owner", or "Multiple roles" on main row
- Actions use fixed 4-slot grid (resend | view | edit | remove) ensuring
icons always align vertically between main rows and sub-rows
- Empty slots render as blank space to maintain alignment
i18n: added multiple_roles key in 4 locales.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Member rows now show a store count with expand/collapse chevron.
Clicking expands sub-rows showing each store with:
- Store name and code
- Per-store role badge
- Per-store status (active/pending independently)
- Per-store actions: resend invitation (pending), remove from store
This fixes the issue where a member active on one store but pending
on another showed misleading combined status and actions.
Member-level actions (view, edit profile) stay on the main row.
Store-level actions (resend, remove) are on each sub-row.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getMemberStatus() showed "pending" if ANY store had a pending invitation,
even if the member was already active in another store. Now checks for
active stores first — a member who is active in at least one store
shows as "active", not "pending".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New standalone page at /store/{store_code}/invitation/accept?token=xxx
where invited team members can:
- Review their name and email (pre-filled from invitation)
- Set their password
- Accept the invitation
Page handles all routing modes (dev path, platform path, prod subdomain,
custom domain) via store context middleware. After acceptance, redirects
to the platform-aware store login page.
New service method get_invitation_info() validates the token and returns
invitation details without modifying anything.
Error states: expired token, already accepted, invalid token.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 new tests in TestResendInvitation:
- test_resend_invitation_for_pending_member: verifies token regeneration
and invitation_sent_at update
- test_resend_invitation_nonexistent_user: verifies 404
Total: 17 store team member integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Using debug flag for environment detection is unreliable — if left
True in prod, links would point to localhost. Now uses the proper
is_production() from environment module.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Email clients need absolute URLs to make links clickable. The
acceptance_link was a relative path (/store/invitation/accept?token=...)
which rendered as plain text. Now prepends the platform domain with
the correct protocol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New resend_invitation() service method regenerates the token and
resends the invitation email for pending members.
Available on all frontends:
- Merchant: POST /merchants/account/team/stores/{sid}/members/{uid}/resend
- Store: POST /store/team/members/{uid}/resend
UI: paper-airplane icon appears on pending members in both merchant
and store team pages.
i18n: resend_invitation + invitation_resent keys in 4 locales.
Also translated previously untranslated invitation_sent_successfully
in fr/de/lb.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
x-model bindings crash when selectedMember is null because x-show
keeps DOM elements alive. x-if removes them entirely, preventing
the "can't access property of null" errors on page load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edit modal now has editable first_name, last_name, email fields with
a "Save Profile" button, alongside the existing per-store role management.
New:
- PUT /merchants/account/team/members/{user_id}/profile endpoint
- MerchantTeamProfileUpdate schema
- update_team_member_profile() service method with ownership validation
- 2 new i18n keys across 4 locales (personal_info, save_profile)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merchant team page:
- Consistent member display (full_name + email on every row)
- New view button (eye icon) on all members including owner
- View modal shows account info (username, role, email verified,
last login, account created) and store memberships with roles
- API enriched with user metadata (username, role, is_email_verified,
last_login, created_at)
Invite fix (both merchant and store routes):
- first_name and last_name from invite form were never passed to the
service that creates the User account. Now passed through correctly.
i18n: 6 new keys across 4 locales (view_member, account_information,
username, email_verified, last_login, account_created).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Login pages don't extend base templates, so they need the
FRONTEND_TYPE injection directly. Fixes "unknown" frontend
in dev toolbar and log prefixes on login pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store team page:
- Fix undefined user_id (API returns `id`, JS used `user_id`)
- Fix wrong URL path in updateMember (remove redundant storeCode)
- Fix update_member_role route passing wrong kwarg (new_role_id → new_role_name)
- Add update_member() service method for role_id + is_active updates
- Add :selected binding for role pre-selection in edit modal
Merchant team page:
- Add missing db.commit() on invite, update, and remove endpoints
- Fix forward-reference string type annotation on MerchantTeamInvite
- Add :selected binding for role pre-selection in edit modal
Shared fixes:
- Replace removed subscription_service.check_team_limit with usage_service
- Replace removed subscription_service.get_current_tier in email service
- Fix email config bool settings crashing on .lower() (value_type=boolean)
Tests: 15 new integration tests for store team member API endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The merchant team page was read-only. Now merchant owners can invite,
edit roles, and remove team members across all their stores from a
single hub view.
Architecture: No new models — delegates to existing store_team_service.
Members are deduplicated across stores with per-store role badges.
New:
- 5 API endpoints: GET team (member-centric), GET store roles, POST
invite (multi-store), PUT update role, DELETE remove member
- merchant-team.js Alpine component with invite/edit/remove modals
- Full CRUD template with stats cards, store filter, member table
- 7 Pydantic schemas for merchant team request/response
- 2 service methods: validate_store_ownership, get_merchant_team_members
- 25 new i18n keys across 4 tenancy locales + 1 core common key
Tests: 434 tenancy tests passing, arch-check green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
merchant_store_service referenced merchant.business_name and
merchant.brand_name which don't exist on the Merchant model.
The field is simply merchant.name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JS was calling /merchants/tenancy/account/team but the endpoint is
mounted at /merchants/account/team (no tenancy prefix in the path).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Template used {% block scripts %} but merchant base.html defines
{% block extra_scripts %}. The merchantTeam() function never rendered,
causing "merchantTeam is not defined" errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merchant team page called .toLowerCase() on a Jinja2 string (Python),
causing UndefinedError. Fixed to .lower().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The tenancy merchant detail page now reads an optional ?back= query
parameter to determine the back button destination. Falls back to
/admin/merchants when no param is present (default behavior preserved).
The loyalty merchant detail "View Merchant" link now passes
?back=/admin/loyalty/merchants/{id} so clicking back from the tenancy
page returns to the loyalty context instead of the merchants list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix IPv6 host parsing with _strip_port() utility
- Remove dangerous StorePlatform→Store.subdomain silent fallback
- Close storefront gate bypass when frontend_type is None
- Add custom subdomain management UI and API for stores
- Add domain health diagnostic tool
- Convert db.add() in loops to db.add_all() (24 PERF-006 fixes)
- Add tests for all new functionality (18 subdomain service tests)
- Add .github templates for validator compliance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The setting `settings.platform_domain` (the global/main domain like "wizard.lu")
was easily confused with `platform.domain` (per-platform domain like "rewardflow.lu").
Renamed to `settings.main_domain` / `MAIN_DOMAIN` env var across the entire codebase.
Also updated docs to reflect the refactored store detection logic with
`is_platform_domain` / `is_subdomain_of_platform` guards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove marketplace page redirect-to-onboarding tests that no longer
match the route behavior. Add can_create_store mock to tenancy store
creation tests to bypass billing limit checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test was failing because can_create_store() called the billing
module's check_resource_limit() which returned False for the test
merchant (no subscription). Patched the limit check since billing
is not what this unit test exercises.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- API-004: Add noqa for factory-pattern auth in user_account routes and payments admin
- MDL-003: Add from_attributes to MerchantStoreDetailResponse schema
- EXC-003: Suppress broad except in merchant_store_service and admin_subscription_service
(intentional fallbacks for optional billing module)
- NAM-002: Rename onboarding files to *_service.py suffix and update all imports
- JS-001: Add file-level noqa for dev-toolbar.js (console interceptor by design)
- JS-005: Add init guards to dashboard.js and customer-detail.js
- IMPORT-004: Break circular deps by removing orders from inventory requires and
marketplace from orders requires; add IMPORT-002 suppression for lazy cross-imports
- MOD-025: Remove unused OnboardingAlreadyCompletedException
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace `except Exception` with specific exception types in
google_wallet_service.py (requests.RequestException, ValueError, etc.)
and apple_wallet_service.py (httpx.HTTPError, OSError, ssl.SSLError)
- Rename loyalty_onboarding.py -> loyalty_onboarding_service.py to
match NAM-002 naming convention (+ test file + imports)
- Add PasswordChangeResponse Pydantic model to user_account API,
removing raw dict return and noqa suppression
Resolves 12 EXC-003 + 1 NAM-002 architecture warnings in loyalty module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes deployment test failures where get_store_usage() and get_merchant_usage()
were called with db=None but attempted to run queries.
Also adds noqa suppressions for pre-existing security validator findings
in dev-toolbar (innerHTML with trusted content) and test fixtures
(hardcoded test passwords).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add admin SQL query tool with saved queries, schema explorer presets,
and collapsible category sections (dev_tools module)
- Add platform debug tool for admin diagnostics
- Add loyalty settings page with owner-only access control
- Fix loyalty settings owner check (use currentUser instead of window.__userData)
- Replace HTTPException with AuthorizationException in loyalty routes
- Expand loyalty module with PIN service, Apple Wallet, program management
- Improve store login with platform detection and multi-platform support
- Update billing feature gates and subscription services
- Add store platform sync improvements and remove is_primary column
- Add unit tests for loyalty (PIN, points, stamps, program services)
- Update i18n translations across dev_tools locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix platform-grouped merchant sidebar menu with core items at root level
- Add merchant store management (detail page, create store, team page)
- Fix store settings 500 error by removing dead stripe/API tab
- Move onboarding translations to module-owned locale files
- Fix onboarding banner i18n with server-side rendering + context inheritance
- Refactor login language selectors to use languageSelector() function (LANG-002)
- Move HTTPException handling to global exception handler in merchant routes (API-003)
- Add language selector to all login pages and portal headers
- Fix customer module: drop order stats from customer model, add to orders module
- Fix admin menu config visibility for super admin platform context
- Fix storefront auth and layout issues
- Add missing i18n translations for onboarding steps (en/fr/de/lb)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 39 documentation files from top-level docs/ into each module's
docs/ folder, accessible via symlinks from docs/modules/. Create
data-model.md files for 10 modules with full schema documentation.
Replace originals with redirect stubs. Remove empty guide stubs.
Modules migrated: tenancy, billing, loyalty, marketplace, orders,
messaging, cms, catalog, inventory, hosting, prospecting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move platforms menu from CMS to Platform Admin section with create/edit
- Add platform create page, API endpoint, and service method
- Remove CMS-specific content from platform list and detail pages
- Create shared entity_selector + entity_selected_badge Jinja macros
- Create entity-selector.js generalizing store-selector.js for any entity
- Add Tom Select merchant filter to stores page with localStorage persistence
- Migrate store-products page to use shared macros (remove 53 lines of duped CSS)
- Fix broken icons: puzzle→puzzle-piece, building-storefront→store, language→translate, server→cube
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add description_translations JSON column to Platform model + migration
- Add language tabs to platform admin edit form for multilingual descriptions
- Update API schemas to include description_translations in request/response
- Translate pricing section UI labels via _t() macro (monthly/annual/CTA/etc.)
- Add Luxembourgish (lb) support to all platforms (OMS, Main, Loyalty, Hosting)
- Seed description_translations, contact emails, and social links for all platforms
- Add LuxWeb Agency demo merchant with hosting stores, team, and content pages
- Fix language code typo: lu → lb in platform-edit.js availableLanguages
- Fix store content pages to use correct primary platform instead of hardcoded OMS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add OnboardingProviderProtocol so modules declare their own post-signup
onboarding steps. The core OnboardingAggregator discovers enabled
providers and exposes a dashboard API (GET /dashboard/onboarding).
A session-scoped banner on the store dashboard shows a checklist that
guides merchants through setup without blocking signup.
Signup is simplified from 4 steps to 3 (Plan → Account → Payment):
store creation is merged into account creation, store language is
captured from the user's browsing language, and platform-specific
template branching is removed.
Includes 47 unit and integration tests covering all new providers,
the aggregator, the API endpoint, and the signup service changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>