feat: migrate to Next.js for Mini App and enhance project structure

- Replaced the previous webapp with a new Mini App built using Next.js, improving performance and maintainability.
- Updated the `.gitignore` to exclude Next.js build artifacts and node modules.
- Revised documentation in `AGENTS.md`, `README.md`, and `architecture.md` to reflect the new Mini App structure and technology stack.
- Enhanced Dockerfile to support the new build process for the Next.js application.
- Updated CI workflow to build and test the Next.js application.
- Added new configuration options for the Mini App, including `MINI_APP_SHORT_NAME` for improved deep linking.
- Refactored frontend testing setup to accommodate the new structure and testing framework.
- Removed legacy webapp files and dependencies to streamline the project.
This commit is contained in:
2026-03-03 16:04:08 +03:00
parent 2de5c1cb81
commit 16bf1a1043
148 changed files with 20240 additions and 7270 deletions

View File

@@ -128,18 +128,32 @@ def _safe_js_string(value: str, allowed: frozenset[str], default: str) -> str:
return default
# Timezone for duty display: allow only safe chars (letters, digits, /, _, -, +) to prevent injection.
_TZ_SAFE_RE = re.compile(r"^[A-Za-z0-9_/+-]{1,50}$")
def _safe_tz_string(value: str) -> str:
"""Return value if it matches safe timezone pattern, else empty string."""
if value and _TZ_SAFE_RE.match(value.strip()):
return value.strip()
return ""
@app.get(
"/app/config.js",
summary="Mini App config (language, log level)",
summary="Mini App config (language, log level, timezone)",
description=(
"Returns JS that sets window.__DT_LANG and window.__DT_LOG_LEVEL. Loaded before main.js."
"Returns JS that sets window.__DT_LANG, window.__DT_LOG_LEVEL and window.__DT_TZ. "
"Loaded before main.js."
),
)
def app_config_js() -> Response:
"""Return JS assigning window.__DT_LANG and window.__DT_LOG_LEVEL for the webapp. No caching."""
"""Return JS assigning window.__DT_LANG, __DT_LOG_LEVEL and __DT_TZ for the webapp. No caching."""
lang = _safe_js_string(config.DEFAULT_LANGUAGE, _VALID_LANGS, "en")
log_level = _safe_js_string(config.LOG_LEVEL_STR.lower(), _VALID_LOG_LEVELS, "info")
body = f'window.__DT_LANG = "{lang}";\nwindow.__DT_LOG_LEVEL = "{log_level}";'
tz = _safe_tz_string(config.DUTY_DISPLAY_TZ)
tz_js = f'\nwindow.__DT_TZ = "{tz}";' if tz else "\nwindow.__DT_TZ = undefined;"
body = f'window.__DT_LANG = "{lang}";\nwindow.__DT_LOG_LEVEL = "{log_level}";{tz_js}'
return Response(
content=body,
media_type="application/javascript; charset=utf-8",
@@ -267,6 +281,7 @@ def get_personal_calendar_ical(
)
webapp_path = config.PROJECT_ROOT / "webapp"
webapp_path = config.PROJECT_ROOT / "webapp-next" / "out"
if webapp_path.is_dir():
app.mount("/app", StaticFiles(directory=str(webapp_path), html=True), name="webapp")