From fa22976e756f0d016cb2e3f6e1b253a64dbd792a Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Fri, 6 Mar 2026 17:51:33 +0300 Subject: [PATCH] feat: enhance Mini App design guidelines and refactor layout components - Updated Mini App design guidelines to include detailed instructions on UI changes, accessibility rules, and verification processes. - Refactored multiple components to utilize `MiniAppScreen` and `MiniAppScreenContent` for consistent layout structure across the application. - Improved error handling in `GlobalError` and `NotFound` components by integrating new layout components for better user experience. - Introduced new hooks for admin functionality, streamlining access checks and data loading processes. - Enhanced documentation to reflect changes in design policies and component usage, ensuring clarity for future development. --- .cursor/rules/frontend.mdc | 6 +- AGENTS.md | 2 +- docs/miniapp-design.md | 46 +++ docs/webapp-next-refactor-audit.md | 119 +++++++ webapp-next/src/app/admin/page.tsx | 52 ++- webapp-next/src/app/global-error.tsx | 25 +- webapp-next/src/app/not-found.tsx | 9 +- webapp-next/src/app/page.tsx | 28 +- webapp-next/src/components/CalendarPage.tsx | 63 +--- .../src/components/admin/ReassignSheet.tsx | 3 +- .../src/components/admin/hooks/index.ts | 5 + .../admin/hooks/use-admin-access.ts | 44 +++ .../admin/hooks/use-admin-duties.ts | 49 +++ .../admin/hooks/use-admin-reassign.ts | 116 ++++++ .../components/admin/hooks/use-admin-users.ts | 45 +++ .../admin/hooks/use-infinite-duty-groups.ts | 56 +++ .../src/components/admin/useAdminPage.ts | 330 +++++------------- .../src/components/calendar/CalendarGrid.tsx | 4 +- .../current-duty/CurrentDutyView.tsx | 89 ++--- .../src/components/day-detail/DayDetail.tsx | 1 + .../src/components/layout/MiniAppScreen.tsx | 55 +++ .../states/FullScreenStateShell.tsx | 17 +- webapp-next/src/components/ui/sheet.tsx | 5 +- webapp-next/src/hooks/telegram/index.ts | 4 + .../telegram/use-telegram-back-button.ts | 44 +++ .../telegram/use-telegram-close-action.ts | 19 + .../use-telegram-interaction-policy.ts | 80 +++++ .../telegram/use-telegram-settings-button.ts | 44 +++ webapp-next/src/hooks/use-request-state.ts | 56 +++ webapp-next/src/hooks/use-screen-ready.ts | 20 ++ webapp-next/src/i18n/messages.ts | 2 + .../src/lib/telegram-interaction-policy.ts | 16 + webapp-next/src/store/app-store.ts | 93 +---- webapp-next/src/store/selectors.ts | 24 ++ .../src/store/slices/calendar-slice.ts | 41 +++ webapp-next/src/store/slices/session-slice.ts | 21 ++ webapp-next/src/store/slices/view-slice.ts | 41 +++ webapp-next/src/store/types.ts | 4 + 38 files changed, 1166 insertions(+), 512 deletions(-) create mode 100644 docs/webapp-next-refactor-audit.md create mode 100644 webapp-next/src/components/admin/hooks/index.ts create mode 100644 webapp-next/src/components/admin/hooks/use-admin-access.ts create mode 100644 webapp-next/src/components/admin/hooks/use-admin-duties.ts create mode 100644 webapp-next/src/components/admin/hooks/use-admin-reassign.ts create mode 100644 webapp-next/src/components/admin/hooks/use-admin-users.ts create mode 100644 webapp-next/src/components/admin/hooks/use-infinite-duty-groups.ts create mode 100644 webapp-next/src/components/layout/MiniAppScreen.tsx create mode 100644 webapp-next/src/hooks/telegram/index.ts create mode 100644 webapp-next/src/hooks/telegram/use-telegram-back-button.ts create mode 100644 webapp-next/src/hooks/telegram/use-telegram-close-action.ts create mode 100644 webapp-next/src/hooks/telegram/use-telegram-interaction-policy.ts create mode 100644 webapp-next/src/hooks/telegram/use-telegram-settings-button.ts create mode 100644 webapp-next/src/hooks/use-request-state.ts create mode 100644 webapp-next/src/hooks/use-screen-ready.ts create mode 100644 webapp-next/src/lib/telegram-interaction-policy.ts create mode 100644 webapp-next/src/store/selectors.ts create mode 100644 webapp-next/src/store/slices/calendar-slice.ts create mode 100644 webapp-next/src/store/slices/session-slice.ts create mode 100644 webapp-next/src/store/slices/view-slice.ts create mode 100644 webapp-next/src/store/types.ts diff --git a/.cursor/rules/frontend.mdc b/.cursor/rules/frontend.mdc index c2c34a4..111f6d6 100644 --- a/.cursor/rules/frontend.mdc +++ b/.cursor/rules/frontend.mdc @@ -41,9 +41,11 @@ The Mini App lives in `webapp-next/`. It is built as a static export and served - **Client components:** Use `'use client'` where hooks or browser APIs are used (layout loads config script; page and most UI are client). - **Theme:** CSS variables in `globals.css`; `useTelegramTheme` maps Telegram theme params to `--tg-theme-*` and sets `data-theme` on ``. - **Auth:** `useTelegramAuth` provides initData for API; access gated for non-Telegram except localhost. -- **i18n:** `useTranslation()` from store lang; `window.__DT_LANG` set by `/app/config.js` (backend). +- **i18n:** `useTranslation()` for React UI; `getLang()/translate()` only for early bootstrap or non-React boundaries. - **API:** `fetchDuties`, `fetchCalendarEvents` in `src/lib/api.ts`; pass initData, lang, AbortSignal; handle ACCESS_DENIED. - **Heavy pages:** For feature-heavy routes (e.g. admin), use a custom hook (state, effects, callbacks) plus presentational components; keep the page as a thin layer (early returns + composition). Example: `admin/page.tsx` uses `useAdminPage`, `AdminDutyList`, and `ReassignSheet`. +- **Platform boundary:** Use `src/hooks/telegram/*` adapters for Back/Settings/Close/swipe/closing behavior instead of direct SDK control calls in feature components. +- **Screen shells:** Reuse `src/components/layout/MiniAppScreen.tsx` wrappers for route and full-screen states. ## Testing @@ -60,6 +62,8 @@ When adding or changing UI in the Mini App, **follow the [Mini App design guidel - Page wrappers: `content-safe`, `max-w-[var(--max-width-app)]`, viewport height; respect safe area for sheets/modals. - Reuse component patterns (buttons, cards, calendar grid, timeline list) and left-stripe semantics (`border-l-duty`, `border-l-today`, etc.). - Add ARIA labels and roles for interactive elements and grids; respect `prefers-reduced-motion` and `data-perf="low"` for animations. +- Keep all user-facing strings and `aria-label`/`sr-only` text localized. +- Follow Telegram interaction policy from the design guideline: vertical swipes enabled by default, closing confirmation only for stateful flows. Use the checklist in the design doc when introducing new screens or components. diff --git a/AGENTS.md b/AGENTS.md index fd85d23..5e4028b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,5 +50,5 @@ Docstrings and code comments must be in English (Google-style docstrings). UI st - **Config:** Environment variables (e.g. `.env`); no hardcoded secrets. - **Database:** One logical transaction per `session_scope` — a single `commit` at the end of the business operation (e.g. in `run_import`). Repository helpers used inside such a flow (e.g. `get_or_create_user_by_full_name`) accept `commit=False` and let the caller commit once. - **Error handling:** Do not send `str(exception)` from parsers or DB to the user. Use generic i18n keys (e.g. `import.parse_error_generic`, `import.import_error_generic`) and log the full exception server-side. -- **Mini App (webapp-next):** When adding or changing UI in `webapp-next/`, follow the [Mini App design guideline](docs/miniapp-design.md): use only design tokens and Tailwind aliases from it, apply layout/safe-area/accessibility rules, and use the checklist for new screens or components. +- **Mini App (webapp-next):** When adding or changing UI in `webapp-next/`, follow the [Mini App design guideline](docs/miniapp-design.md): use only design tokens and Tailwind aliases, use shared Mini App screen shells, keep Telegram SDK access behind `src/hooks/telegram/*`, apply safe-area/content-safe-area and accessibility rules, and run the Mini App verification matrix (light/dark, iOS/Android safe area, low-perf Android, deep links, admin flow, fallback states). - **Cursor:** The project does not version `.cursor/`. You can mirror this file in `.cursor/rules/` locally; [AGENTS.md](AGENTS.md) is the single versioned reference for AI and maintainers. diff --git a/docs/miniapp-design.md b/docs/miniapp-design.md index d5dc4e9..cfcbbd6 100644 --- a/docs/miniapp-design.md +++ b/docs/miniapp-design.md @@ -100,6 +100,12 @@ Use **only** these tokens and Tailwind/shadcn aliases (`bg-background`, `text-mu - **Sticky headers:** Use `top-[var(--app-safe-top)]` (not `top-0`) for sticky elements (e.g. calendar header, admin header) so they sit below the Telegram UI instead of overlapping it. - Lists that extend to the bottom should also account for bottom inset (e.g. `padding-bottom: var(--tg-viewport-content-safe-area-inset-bottom, 12px)` in `.container-app`). +Official terminology (Telegram docs) uses `safeAreaInset` and `contentSafeAreaInset` +plus events `safeAreaChanged` and `contentSafeAreaChanged`. In our code, these values +are exposed through SDK CSS bindings and consumed via app aliases (`--app-safe-*`). +When updating safe-area code, preserve this mapping and avoid mixing raw `env(...)` +and Telegram content-safe insets within the same component. + ### 3.4 Sheets and modals Bottom sheets and modals that sit at the bottom of the screen must add safe area to their padding, e.g.: @@ -203,6 +209,31 @@ See `webapp-next/src/components/day-detail/DayDetail.tsx` for the Sheet content. - `setBottomBarColor('bottom_bar_bg_color')` when available (Bot API 7.10+). - **Surface contrast:** When `--surface` equals `--bg` (e.g. some iOS OLED themes), `fixSurfaceContrast()` in `use-telegram-theme.ts` adjusts `--surface` using ThemeParams or a light color-mix so cards and panels remain visible. +### 8.1 Native control policy + +- Use platform wrappers in `src/hooks/telegram/` rather than direct SDK calls in + feature components. +- **BackButton:** preferred for route-level back navigation in Telegram context. +- **SettingsButton:** use for route actions like opening `/admin` from calendar. +- **Main/Secondary button:** optional; use only if action must align with Telegram + bottom action affordance (do not duplicate with conflicting in-app primary CTA). +- **Haptics:** trigger only on meaningful user actions (submit, confirm, close). + +### 8.2 Swipe and closing policy + +- Keep vertical swipes enabled by default (`enableVerticalSwipes` behavior). +- Disable vertical swipes only on screens with explicit gesture conflict and document + the reason in code review. +- Enable closing confirmation only for stateful flows where accidental close can + lose user intent (e.g. reassignment flow in admin sheet). + +### 8.3 Fullscreen/newer APIs policy + +- Fullscreen APIs (`requestFullscreen`, `exitFullscreen`) are currently optional and + out of scope unless a feature explicitly requires immersive mode. +- If fullscreen is introduced, review safe area/content safe area and verify + `safeAreaChanged`, `contentSafeAreaChanged`, and `fullscreenChanged` handling. + --- ## 9. Checklist for new screens and components @@ -215,3 +246,18 @@ Use this for review when adding or changing UI: - [ ] Safe area is respected for bottom padding and for sheets/modals. - [ ] Interactive elements and grids/lists have appropriate `aria-label`s and roles. - [ ] New animations respect `prefers-reduced-motion` and `data-perf="low"` (short or minimal on low-end Android). +- [ ] User-facing strings and `aria-label`/`sr-only` text are localized via i18n (no hardcoded English in shared UI). +- [ ] Telegram controls are connected through platform hooks (`src/hooks/telegram/*`) instead of direct SDK calls. +- [ ] Vertical swipe and closing confirmation behavior follows the policy above. + +## 10. Verification matrix (Mini App) + +At minimum verify: + +- Telegram light + dark themes. +- iOS and Android safe area/content-safe-area behavior (portrait + landscape). +- Android low-performance behavior (`data-perf="low"`). +- Deep link current duty (`startParam=duty`). +- Direct `/admin` open and reassignment flow. +- Access denied, not-found, and error boundary screens. +- Calendar swipe navigation with sticky header and native Telegram controls. diff --git a/docs/webapp-next-refactor-audit.md b/docs/webapp-next-refactor-audit.md new file mode 100644 index 0000000..8376ab8 --- /dev/null +++ b/docs/webapp-next-refactor-audit.md @@ -0,0 +1,119 @@ +# Webapp-next Refactor Baseline Audit + +This note captures the baseline before the phased refactor. It defines current risks, +duplication hotspots, and expected behavior that must not regress. + +## 1) Screens and boundaries + +- Home route orchestration: `webapp-next/src/app/page.tsx` + - Chooses among `AccessDeniedScreen`, `CurrentDutyView`, `CalendarPage`. + - Controls app visibility via `appContentReady`. +- Admin route orchestration: `webapp-next/src/app/admin/page.tsx` + - Thin route, but still owns shell duplication and content-ready signaling. +- Calendar composition root: `webapp-next/src/components/CalendarPage.tsx` + - Combines sticky layout, swipe, month loading, auto-refresh, settings button. +- Current duty feature root: `webapp-next/src/components/current-duty/CurrentDutyView.tsx` + - Combines data loading, error/access states, back button, and close action. +- Admin feature state root: `webapp-next/src/components/admin/useAdminPage.ts` + - Combines SDK button handling, admin access, users/duties loading, sheet state, + mutation and infinite scroll concerns. + +## 2) Telegram integration touchpoints + +- SDK/provider bootstrap: + - `webapp-next/src/components/providers/TelegramProvider.tsx` + - `webapp-next/src/components/ReadyGate.tsx` + - `webapp-next/src/lib/telegram-ready.ts` +- Direct control usage in feature code: + - `backButton` in `CurrentDutyView` and `useAdminPage` + - `settingsButton` in `CalendarPage` + - `closeMiniApp` in `CurrentDutyView` +- Haptics in feature-level handlers: + - `webapp-next/src/lib/telegram-haptic.ts` + +Risk: platform behavior is spread across feature components instead of a narrow +platform boundary. + +## 3) Layout and shell duplication + +Repeated outer wrappers appear across route and state screens: +- `content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background` +- `mx-auto flex w-full max-w-[var(--max-width-app)] flex-col` + +Known locations: +- `webapp-next/src/app/page.tsx` +- `webapp-next/src/app/admin/page.tsx` +- `webapp-next/src/components/CalendarPage.tsx` +- `webapp-next/src/components/states/FullScreenStateShell.tsx` +- `webapp-next/src/app/not-found.tsx` +- `webapp-next/src/app/global-error.tsx` + +Risk: future safe-area or viewport fixes require multi-file edits. + +## 4) Readiness and lifecycle coupling + +`appContentReady` is set by multiple screens/routes: +- `page.tsx` +- `admin/page.tsx` +- `CalendarPage.tsx` +- `CurrentDutyView.tsx` + +`ReadyGate` is route-agnostic, but signaling is currently ad hoc. +Risk: race conditions or deadlock-like "hidden app" scenarios when screen states +change in future refactors. + +## 5) Async/data-loading duplication + +Repeated manual patterns (abort, retries, state machine): +- `webapp-next/src/hooks/use-month-data.ts` +- `webapp-next/src/components/current-duty/CurrentDutyView.tsx` +- `webapp-next/src/components/admin/useAdminPage.ts` + +Risk: inconsistent retry/access-denied behavior and difficult maintenance. + +## 6) Store mixing concerns + +`webapp-next/src/store/app-store.ts` currently mixes: +- session/platform concerns (`lang`, `appContentReady`, `isAdmin`) +- calendar/domain concerns (`currentMonth`, `pendingMonth`, duties/events) +- view concerns (`currentView`, `selectedDay`, `error`, `accessDenied`) + +Risk: high coupling and larger blast radius for otherwise local changes. + +## 7) i18n/a11y gaps to close + +- Hardcoded grid label in `CalendarGrid`: `aria-label="Calendar"`. +- Hardcoded sr-only close text in shared `Sheet`: `"Close"`. +- Mixed language access strategy (`useTranslation()` vs `getLang()/translate()`), + valid for bootstrap/error boundary, but not explicitly codified in one place. + +## 8) Telegram Mini Apps compliance checklist (baseline) + +Already implemented well: +- Dynamic theme + runtime sync. +- Safe-area/content-safe-area usage via CSS vars and layout classes. +- `ready()` gate and Telegram loader handoff. +- Android low-performance class handling. + +Needs explicit policy/consistency: +- Vertical swipes policy for gesture-heavy screens. +- Closing confirmation policy for stateful admin flows. +- Main/Secondary button usage policy for primary actions. +- Terminology alignment with current official docs: + `safeAreaInset`, `contentSafeAreaInset`, fullscreen events. + +## 9) Expected behavior (non-regression) + +- `/`: + - Shows access denied screen if not allowed. + - Opens current-duty view for `startParam=duty`. + - Otherwise opens calendar. +- `/admin`: + - Denies non-admin users. + - Loads users and duties for selected admin month. + - Allows reassignment with visible feedback. +- Error/fallback states: + - `not-found` and global error remain full-screen and theme-safe. +- Telegram UX: + - Back/settings controls remain functional in Telegram context. + - Ready handoff happens when first useful screen is visible. diff --git a/webapp-next/src/app/admin/page.tsx b/webapp-next/src/app/admin/page.tsx index fca4f10..5de7591 100644 --- a/webapp-next/src/app/admin/page.tsx +++ b/webapp-next/src/app/admin/page.tsx @@ -6,64 +6,54 @@ "use client"; -import { useEffect } from "react"; import { useAdminPage, AdminDutyList, ReassignSheet } from "@/components/admin"; import { useTranslation } from "@/i18n/use-translation"; -import { useAppStore } from "@/store/app-store"; import { MonthNavHeader } from "@/components/calendar/MonthNavHeader"; import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen"; import { LoadingState } from "@/components/states/LoadingState"; import { ErrorState } from "@/components/states/ErrorState"; - -const OUTER_CLASS = - "content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background"; -const INNER_CLASS = - "mx-auto flex w-full max-w-[var(--max-width-app)] flex-col"; +import { MiniAppScreen, MiniAppScreenContent, MiniAppStickyHeader } from "@/components/layout/MiniAppScreen"; +import { useScreenReady } from "@/hooks/use-screen-ready"; export default function AdminPage() { const { t, monthName } = useTranslation(); const admin = useAdminPage(); - const setAppContentReady = useAppStore((s) => s.setAppContentReady); - - // Signal ready so Telegram hides loader when opening /admin directly. - useEffect(() => { - setAppContentReady(true); - }, [setAppContentReady]); + useScreenReady(true); if (!admin.isAllowed) { return ( -
-
+ + -
-
+ + ); } if (admin.adminCheckComplete === null) { return ( -
-
+ +

{t("admin.loading_users")}

-
-
+ + ); } if (admin.adminAccessDenied) { return ( -
-
+ +

{admin.adminAccessDeniedDetail ?? t("admin.access_denied")}

-
-
+ + ); } @@ -71,9 +61,9 @@ export default function AdminPage() { const year = admin.adminMonth.getFullYear(); return ( -
-
-
+ + + -
+ {admin.successMessage && (

@@ -135,7 +125,7 @@ export default function AdminPage() { onCloseAnimationEnd={admin.closeReassign} t={t} /> -

-
+ + ); } diff --git a/webapp-next/src/app/global-error.tsx b/webapp-next/src/app/global-error.tsx index 509d0c1..a04eaec 100644 --- a/webapp-next/src/app/global-error.tsx +++ b/webapp-next/src/app/global-error.tsx @@ -11,6 +11,7 @@ import { getLang, translate } from "@/i18n/messages"; import { callMiniAppReadyOnce } from "@/lib/telegram-ready"; import { THEME_BOOTSTRAP_SCRIPT } from "@/lib/theme-bootstrap-script"; import { Button } from "@/components/ui/button"; +import { MiniAppScreen, MiniAppScreenContent } from "@/components/layout/MiniAppScreen"; export default function GlobalError({ error, @@ -35,17 +36,19 @@ export default function GlobalError({