feat(tenancy): add team invitation acceptance page
Some checks failed
CI / ruff (push) Successful in 14s
CI / pytest (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

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>
This commit is contained in:
2026-03-29 23:05:23 +02:00
parent 01f7add8dd
commit 11dcfdad73
3 changed files with 308 additions and 1 deletions

View File

@@ -10,7 +10,7 @@ Store pages for authentication and account management:
- Settings
"""
from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
@@ -100,6 +100,43 @@ async def store_login_page(
)
@router.get(
"/invitation/accept", response_class=HTMLResponse, include_in_schema=False
)
async def store_invitation_accept_page(
request: Request,
token: str = Query(..., description="Invitation token from email"),
store_code: str = Depends(get_resolved_store_code),
db: Session = Depends(get_db),
):
"""
Render invitation acceptance page.
Public route — no auth required. Validates the token and shows a form
to review name and set password.
"""
from app.modules.tenancy.services.store_team_service import store_team_service
language = getattr(request.state, "language", "en")
platform_code = getattr(request.state, "platform_code", None)
# Get invitation info (without accepting it)
info = store_team_service.get_invitation_info(db, token)
return templates.TemplateResponse(
"tenancy/store/invitation-accept.html",
{
"request": request,
"store_code": store_code,
"platform_code": platform_code,
"frontend_type": "store",
"token": token,
"invitation": info,
**get_jinja2_globals(language),
},
)
# ============================================================================
# AUTHENTICATED ROUTES (Store Users Only)
# ============================================================================