Skip to content

(SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs#372

Merged
ViktorSvertoka merged 4 commits intodevelopfrom
bug/fix
Feb 26, 2026
Merged

(SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs#372
ViktorSvertoka merged 4 commits intodevelopfrom
bug/fix

Conversation

@ViktorSvertoka
Copy link
Member

@ViktorSvertoka ViktorSvertoka commented Feb 26, 2026

Summary

This PR optimizes client auth state handling to reduce host/DB load and remove auth UI flicker during locale switches and multi-tab usage.

Summary by CodeRabbit

New Features

  • Authentication state now automatically synchronizes across all browser tabs and windows for consistent session management.
  • Implemented client-side caching for authentication requests to reduce unnecessary API calls and improve performance.

Updates

  • Refined the authentication response structure for enhanced consistency and reliability.

@vercel
Copy link
Contributor

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devlovers-net Ignored Ignored Preview Feb 26, 2026 1:17pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR implements cross-tab authentication state synchronization and optimizes auth fetching by introducing client-side caching with TTL, request deduplication, and a BroadcastChannel-based sync mechanism. The auth/me endpoint payload is simplified to omit username. Components now trigger broadcasts on auth state changes.

Changes

Cohort / File(s) Summary
API & Core Auth Logic
frontend/app/api/auth/me/route.ts, frontend/lib/auth.ts
Changed auth endpoint to use new getAuthSession() helper; removed username from response payload. Introduced AuthSession type and getAuthSession() function to standardize session data shape.
Auth Hook Optimization
frontend/hooks/useAuth.tsx
Major refactor: added client-side caching with TTL, deduplicates in-flight auth requests, replaced AbortController with focus-based refresh, subscribes to external auth updates, and removed username from exposed user data.
Cross-Tab Synchronization
frontend/lib/auth-sync.ts
New utility module exposing broadcastAuthUpdated() and subscribeToAuthUpdates() functions using BroadcastChannel for coordinating auth state changes across browser tabs.
Component Auth Integration
frontend/components/auth/LoginForm.tsx, frontend/components/auth/SignupForm.tsx, frontend/components/q&a/AIWordHelper.tsx
LoginForm and SignupForm now invoke broadcastAuthUpdated() post-login/signup. AIWordHelper replaces per-instance API call with useAuth hook for centralized auth status.
Logout Integration
frontend/lib/logout.ts
Added broadcastAuthUpdated() call after logout request to trigger cross-tab sync.

Sequence Diagram(s)

sequenceDiagram
    participant Tab1 as User Tab 1
    participant API as Auth API
    participant BC as BroadcastChannel
    participant Tab2 as User Tab 2
    participant Hook as useAuth Hook

    Note over Tab1,Tab2: Cross-Tab Auth Sync Flow

    Tab1->>API: POST /login (credentials)
    API-->>Tab1: auth token
    Tab1->>Tab1: LoginForm calls broadcastAuthUpdated()
    Tab1->>BC: Post AUTH_UPDATED event
    
    Note over Tab2: Receives broadcast
    Tab2->>BC: subscribeToAuthUpdates triggered
    Tab2->>Hook: Force refresh auth state
    Hook->>API: GET /api/auth/me
    API-->>Hook: Return cached session {id,role}
    Hook->>Hook: Update cache + timestamp
    Hook-->>Tab2: Context updated, components re-render
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

performance

Suggested reviewers

  • AM1007

Poem

🐰 Hops through tabs with glee,
Auth broadcasts sync with care,
Cache and dedupe so free,
Sessions floating everywhere,
Cross-tab magic in the air!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objectives: reducing auth overhead and syncing auth state across tabs, which are the core focuses of the changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
frontend/lib/auth.ts (1)

117-131: Consider renaming variable for clarity.

The variable payload at line 118 is actually an AuthSession object, not a raw token payload. Consider renaming to session for clarity:

 export async function getCurrentUser(): Promise<AuthUser | null> {
-  const payload = await getAuthSession();
-  if (!payload) {
+  const session = await getAuthSession();
+  if (!session) {
     return null;
   }

   const result = await db
     .select({
       id: users.id,
       email: users.email,
       role: users.role,
       username: users.name,
     })
     .from(users)
-    .where(eq(users.id, payload.id))
+    .where(eq(users.id, session.id))
     .limit(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/auth.ts` around lines 117 - 131, In getCurrentUser(), rename the
local variable payload (the result of getAuthSession()) to session to accurately
reflect its type; update the declaration and all subsequent references in the
function (including the where(eq(users.id, payload.id)) check) to use
session.id, and ensure any related imports/usages in getCurrentUser remain
consistent (identify via function name getCurrentUser and call to
getAuthSession).
frontend/hooks/useAuth.tsx (2)

96-107: Error state is cached for the full TTL.

When an error occurs, authUserCache is set to null (not undefined), which means isCacheFresh() will return true for the next 60 seconds. This prevents automatic recovery from transient errors during that window—though explicit refresh() calls and cross-tab broadcasts will still bypass the cache via force: true.

If faster recovery from transient errors is desired, consider either:

  • Using a shorter TTL for error states, or
  • Setting authUserCache = undefined on error to allow immediate retry

Current behavior is acceptable if the intent is to prevent hammering the server after errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/hooks/useAuth.tsx` around lines 96 - 107, The error case currently
sets authUserCache = null and authUserCacheAt = Date.now(), which makes
isCacheFresh() treat the error state as fresh for the full TTL and blocks
automatic retries; change the error handling in the catch block of useAuth (the
section that checks DOMException/AbortError, latestRequestIdRef.current vs
requestId, then sets authUserCache/authUserCacheAt and calls setUser) to instead
set authUserCache = undefined (or alternatively set a much shorter
authUserCacheAt TTL) so that isCacheFresh() will not block immediate retry
attempts (while still keeping explicit refresh() and force:true behavior
intact).

57-67: Unused AbortError handling.

fetchAuthDeduped() doesn't pass an AbortSignal to fetchAuth(), so the AbortError handling at lines 97-99 is effectively dead code. Either:

  • Remove the abort handling since requests are no longer cancellable, or
  • Add abort support to fetchAuthDeduped if cancellation is desired
Option 1: Remove dead code
     } catch (error) {
-      if (error instanceof DOMException && error.name === 'AbortError') {
-        return;
-      }
-
       if (latestRequestIdRef.current !== requestId) {
         return;
       }

Also applies to: 97-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/hooks/useAuth.tsx` around lines 57 - 67, The deduping helper
fetchAuthDeduped currently calls fetchAuth() with no AbortSignal, so the
AbortError handling in the surrounding auth flow (e.g., the try/catch that
checks for AbortError) is ineffective; either remove that dead AbortError branch
or make fetchAuthDeduped support cancellation by accepting an AbortSignal and
forwarding it into fetchAuth (and ensuring inFlightAuthPromise is cleared on
finally/abort). Update the function signature fetchAuthDeduped(signal?:
AbortSignal) and call fetchAuth({ signal }) (or remove the AbortError catch
where fetchAuthDeduped is used) so the abort path becomes meaningful and
inFlightAuthPromise lifecycle remains correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/hooks/useAuth.tsx`:
- Around line 96-107: The error case currently sets authUserCache = null and
authUserCacheAt = Date.now(), which makes isCacheFresh() treat the error state
as fresh for the full TTL and blocks automatic retries; change the error
handling in the catch block of useAuth (the section that checks
DOMException/AbortError, latestRequestIdRef.current vs requestId, then sets
authUserCache/authUserCacheAt and calls setUser) to instead set authUserCache =
undefined (or alternatively set a much shorter authUserCacheAt TTL) so that
isCacheFresh() will not block immediate retry attempts (while still keeping
explicit refresh() and force:true behavior intact).
- Around line 57-67: The deduping helper fetchAuthDeduped currently calls
fetchAuth() with no AbortSignal, so the AbortError handling in the surrounding
auth flow (e.g., the try/catch that checks for AbortError) is ineffective;
either remove that dead AbortError branch or make fetchAuthDeduped support
cancellation by accepting an AbortSignal and forwarding it into fetchAuth (and
ensuring inFlightAuthPromise is cleared on finally/abort). Update the function
signature fetchAuthDeduped(signal?: AbortSignal) and call fetchAuth({ signal })
(or remove the AbortError catch where fetchAuthDeduped is used) so the abort
path becomes meaningful and inFlightAuthPromise lifecycle remains correct.

In `@frontend/lib/auth.ts`:
- Around line 117-131: In getCurrentUser(), rename the local variable payload
(the result of getAuthSession()) to session to accurately reflect its type;
update the declaration and all subsequent references in the function (including
the where(eq(users.id, payload.id)) check) to use session.id, and ensure any
related imports/usages in getCurrentUser remain consistent (identify via
function name getCurrentUser and call to getAuthSession).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e3526f and 7696147.

📒 Files selected for processing (8)
  • frontend/app/api/auth/me/route.ts
  • frontend/components/auth/LoginForm.tsx
  • frontend/components/auth/SignupForm.tsx
  • frontend/components/q&a/AIWordHelper.tsx
  • frontend/hooks/useAuth.tsx
  • frontend/lib/auth-sync.ts
  • frontend/lib/auth.ts
  • frontend/lib/logout.ts

@ViktorSvertoka ViktorSvertoka merged commit 9130df1 into develop Feb 26, 2026
7 checks passed
@ViktorSvertoka ViktorSvertoka deleted the bug/fix branch February 26, 2026 13:26
@coderabbitai coderabbitai bot mentioned this pull request Feb 26, 2026
ViktorSvertoka added a commit that referenced this pull request Feb 26, 2026
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364)

* (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367)

* perf(vercel): cut runtime costs via notification, blog cache, and analytics changes

* perf(blog): remove server searchParams usage to preserve ISR

* fix(build): align Netlify Node version and remove SpeedInsights import

* chore(release): bump version to 1.0.4

* (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* feat(frontend): sync auth state across tabs via BroadcastChannel

* (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* fix: eliminate quiz timer flash on language switch

Remove Suspense boundary (loading.tsx) that unmounted QuizContainer
during locale navigation. Synchronous session restore via useReducer
lazy initializer and correct timer initialization via useState lazy
initializer prevent any visible state reset on language switch

* fix: replace quiz card layout shift with skeleton grid during progress load

* chore(release): v1.0.5

---------

Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Feb 27, 2026
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364)

* (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367)

* perf(vercel): cut runtime costs via notification, blog cache, and analytics changes

* perf(blog): remove server searchParams usage to preserve ISR

* fix(build): align Netlify Node version and remove SpeedInsights import

* chore(release): bump version to 1.0.4

* (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* feat(frontend): sync auth state across tabs via BroadcastChannel

* (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* fix: eliminate quiz timer flash on language switch

Remove Suspense boundary (loading.tsx) that unmounted QuizContainer
during locale navigation. Synchronous session restore via useReducer
lazy initializer and correct timer initialization via useState lazy
initializer prevent any visible state reset on language switch

* fix: replace quiz card layout shift with skeleton grid during progress load

* chore(release): v1.0.5

* (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6)

* (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors

* Add shop category images to public

* (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks

* (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED)

* (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants

* (SP: 3) [Shop][Monobank]  review fixes (tests, logging, success UI)

* (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups

* (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing

* (SP: 1) [Cart] adding route for user orders to cart page

* (SP: 1) [Cart] fix after review cart mpage and adding index for orders

* (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge

* (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change

* (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling)

* (SP: 2)[Backend] shop/shipping schema migrations foundation

* (SP: 2)[Backend] shop/shipping public routes + np cache + sync

* (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy

* (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping

* (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock

* (SP: 2)[Backend] shop/shipping: admin+ui shipping actions

* (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics

* (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests

* (SP: 1) [db]: add shop shipping core migration

* (SP: 1) [FIX] resolve merge artifacts in order details page

* (SP: 1) [FIX] apply post-review fixes for shipping and admin flows

* (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code)

* (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping

* (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed

* (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout.

* (SP: 1) [FIX] migration 17

* (SP: 1) [DB] migrarion to testind DB and adjusting tests

* (SP: 1)[DB] slow down restock janitor + enforce prod interval floor

* (SP: 1) [DB] add order status lite view (opt-in) + instrumentation

* (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling

* (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant)

* (SP: 1)[DB] enforce production min intervals for internal shipping jobs

* (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes

* (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching

* (SP: 1)[DB]: slow janitor schedule to every 30 minutes

* (SP: 1)[DB] increase polling delays for MonobankRedirectStatus

* (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor

* (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor

---------

Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Mar 1, 2026
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364)

* (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367)

* perf(vercel): cut runtime costs via notification, blog cache, and analytics changes

* perf(blog): remove server searchParams usage to preserve ISR

* fix(build): align Netlify Node version and remove SpeedInsights import

* chore(release): bump version to 1.0.4

* (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* feat(frontend): sync auth state across tabs via BroadcastChannel

* (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* fix: eliminate quiz timer flash on language switch

Remove Suspense boundary (loading.tsx) that unmounted QuizContainer
during locale navigation. Synchronous session restore via useReducer
lazy initializer and correct timer initialization via useState lazy
initializer prevent any visible state reset on language switch

* fix: replace quiz card layout shift with skeleton grid during progress load

* chore(release): v1.0.5

* (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6)

* (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors

* Add shop category images to public

* (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks

* (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED)

* (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants

* (SP: 3) [Shop][Monobank]  review fixes (tests, logging, success UI)

* (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups

* (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing

* (SP: 1) [Cart] adding route for user orders to cart page

* (SP: 1) [Cart] fix after review cart mpage and adding index for orders

* (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge

* (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change

* (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling)

* (SP: 2)[Backend] shop/shipping schema migrations foundation

* (SP: 2)[Backend] shop/shipping public routes + np cache + sync

* (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy

* (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping

* (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock

* (SP: 2)[Backend] shop/shipping: admin+ui shipping actions

* (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics

* (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests

* (SP: 1) [db]: add shop shipping core migration

* (SP: 1) [FIX] resolve merge artifacts in order details page

* (SP: 1) [FIX] apply post-review fixes for shipping and admin flows

* (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code)

* (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping

* (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed

* (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout.

* (SP: 1) [FIX] migration 17

* (SP: 1) [DB] migrarion to testind DB and adjusting tests

* (SP: 1)[DB] slow down restock janitor + enforce prod interval floor

* (SP: 1) [DB] add order status lite view (opt-in) + instrumentation

* (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling

* (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant)

* (SP: 1)[DB] enforce production min intervals for internal shipping jobs

* (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes

* (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching

* (SP: 1)[DB]: slow janitor schedule to every 30 minutes

* (SP: 1)[DB] increase polling delays for MonobankRedirectStatus

* (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor

* (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor

* (SP: 3) [SHOP] audit-driven e2e purchase readiness hardening (events, notifications, consent, returns) (#378)

* (SP:3)[SHOP] add canonical payment/shipping/admin audit tables + dedupe helper with flagged atomic dual-write

* (SP: 2)[SHOP] add INTL quote flow (request/offer/accept/decline), payment-init gate, and quote expiry/timeout sweeps

* (SP: 3)[SHOP] introduce outbox-driven notifications with projector + worker (phase 3)

* (SP: 3)[SHOP] add minimal returns/RMA lifecycle with atomic audit + canonical events (phase 4)

* (SP: 3)[SHOP] enforce guest status-token lite-only access and audit token usage (phase 5)

* (SP: 3)[SHOP] centralize transition guards and enforce across admin/webhook/worker flows (phase 6)

:wq
n

* (SP:3)[SHOP]: enforce DATABASE_URL_LOCAL preflight + deterministic vitest config

* (SP:3)[SHOP]: require canonical events in prod (fail-fast)

* (SP:3)[SHOP]: implement notifications transport with retries + DLQ

* (SP:3)[SHOP]: persist checkout legal consent artifact

* (SP:1)[SHOP] add migration 0025 for consent + events + audit + prices

* (SP:1)[SHOP] emit shipping_events for shipment worker transitions

* (SP:2)[SHOP] audit admin product mutations (deduped)

* (SP:2)[SHOP] make Monobank webhook retryable on transient apply failures

* (SP:3) [SHOP] enforce guest status-token scopes across order actions

* (SP:1) [SHOP] make product_prices the only write authority

* (SP:1) [SHOP]  add Playwright e2e smoke suite (local DB only)

* (SP:3) [SHOP] explicitly reject exchanges (EXCHANGES_NOT_SUPPORTED)

* (SP: 3)[FIX] harden audit + workers/tests; fix transitions/restock + webhook perf

* (SP: 3)[FIX] harden local-db test safety and tighten shop reliability guards

* (SP: 3)[FIX] fail-closed admin audit, refine shipments-worker outcomes/metrics, tighten quote+tests

* (SP: 1)[FIX] harden shipments-worker claiming/leases and make audit+quote/test paths resilient

* (SP: 1)[FIX] harden quote request errors/logging and sanitize requestId; document best-effort delete audit

* feat(ui): add devops/cloud category icons and styles (#379)

* chore(release): prepare v1.0.6 changelog

* chore: bump version to 1.0.6

---------

Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Mar 1, 2026
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364)

* (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367)

* perf(vercel): cut runtime costs via notification, blog cache, and analytics changes

* perf(blog): remove server searchParams usage to preserve ISR

* fix(build): align Netlify Node version and remove SpeedInsights import

* chore(release): bump version to 1.0.4

* (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372)

* refactor(frontend): remove locale layout dynamic auth and move header auth client-side

* fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout

* feat(frontend): sync auth state across tabs via BroadcastChannel

* (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373)

* perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page

- Move user progress fetch from SSR to client-side API (/api/quiz/progress)
- Remove force-dynamic and getCurrentUser() from quizzes page
- Add revalidate=300 for ISR caching
- Use window.history.replaceState for tab URL sync (avoid Next.js navigation)
- Add forceMount to TabsContent to prevent layout shift on tab switch
- Fix nested <main> — use <section> inside DynamicGridBackground
- Cache GitHub stars count in sessionStorage to avoid refetch + re-animation

* perf: replace useRef with useState lazy initializer in GitHubStarButton

Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot
be read during render. Uses useState(getStoredStars) to capture the
sessionStorage value once on mount instead.

* fix: stop star icon trembling on hover in GitHubStarButton

* fix: eliminate quiz timer flash on language switch

Remove Suspense boundary (loading.tsx) that unmounted QuizContainer
during locale navigation. Synchronous session restore via useReducer
lazy initializer and correct timer initialization via useState lazy
initializer prevent any visible state reset on language switch

* fix: replace quiz card layout shift with skeleton grid during progress load

* chore(release): v1.0.5

* (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6)

* (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors

* Add shop category images to public

* (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks

* (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED)

* (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants

* (SP: 3) [Shop][Monobank]  review fixes (tests, logging, success UI)

* (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups

* (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing

* (SP: 1) [Cart] adding route for user orders to cart page

* (SP: 1) [Cart] fix after review cart mpage and adding index for orders

* (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge

* (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change

* (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling)

* (SP: 2)[Backend] shop/shipping schema migrations foundation

* (SP: 2)[Backend] shop/shipping public routes + np cache + sync

* (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy

* (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping

* (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock

* (SP: 2)[Backend] shop/shipping: admin+ui shipping actions

* (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics

* (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests

* (SP: 1) [db]: add shop shipping core migration

* (SP: 1) [FIX] resolve merge artifacts in order details page

* (SP: 1) [FIX] apply post-review fixes for shipping and admin flows

* (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code)

* (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping

* (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed

* (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout.

* (SP: 1) [FIX] migration 17

* (SP: 1) [DB] migrarion to testind DB and adjusting tests

* (SP: 1)[DB] slow down restock janitor + enforce prod interval floor

* (SP: 1) [DB] add order status lite view (opt-in) + instrumentation

* (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling

* (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant)

* (SP: 1)[DB] enforce production min intervals for internal shipping jobs

* (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes

* (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching

* (SP: 1)[DB]: slow janitor schedule to every 30 minutes

* (SP: 1)[DB] increase polling delays for MonobankRedirectStatus

* (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor

* (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor

* (SP: 3) [SHOP] audit-driven e2e purchase readiness hardening (events, notifications, consent, returns) (#378)

* (SP:3)[SHOP] add canonical payment/shipping/admin audit tables + dedupe helper with flagged atomic dual-write

* (SP: 2)[SHOP] add INTL quote flow (request/offer/accept/decline), payment-init gate, and quote expiry/timeout sweeps

* (SP: 3)[SHOP] introduce outbox-driven notifications with projector + worker (phase 3)

* (SP: 3)[SHOP] add minimal returns/RMA lifecycle with atomic audit + canonical events (phase 4)

* (SP: 3)[SHOP] enforce guest status-token lite-only access and audit token usage (phase 5)

* (SP: 3)[SHOP] centralize transition guards and enforce across admin/webhook/worker flows (phase 6)

:wq
n

* (SP:3)[SHOP]: enforce DATABASE_URL_LOCAL preflight + deterministic vitest config

* (SP:3)[SHOP]: require canonical events in prod (fail-fast)

* (SP:3)[SHOP]: implement notifications transport with retries + DLQ

* (SP:3)[SHOP]: persist checkout legal consent artifact

* (SP:1)[SHOP] add migration 0025 for consent + events + audit + prices

* (SP:1)[SHOP] emit shipping_events for shipment worker transitions

* (SP:2)[SHOP] audit admin product mutations (deduped)

* (SP:2)[SHOP] make Monobank webhook retryable on transient apply failures

* (SP:3) [SHOP] enforce guest status-token scopes across order actions

* (SP:1) [SHOP] make product_prices the only write authority

* (SP:1) [SHOP]  add Playwright e2e smoke suite (local DB only)

* (SP:3) [SHOP] explicitly reject exchanges (EXCHANGES_NOT_SUPPORTED)

* (SP: 3)[FIX] harden audit + workers/tests; fix transitions/restock + webhook perf

* (SP: 3)[FIX] harden local-db test safety and tighten shop reliability guards

* (SP: 3)[FIX] fail-closed admin audit, refine shipments-worker outcomes/metrics, tighten quote+tests

* (SP: 1)[FIX] harden shipments-worker claiming/leases and make audit+quote/test paths resilient

* (SP: 1)[FIX] harden quote request errors/logging and sanitize requestId; document best-effort delete audit

* feat(ui): add devops/cloud category icons and styles (#379)

* chore(release): prepare v1.0.6 changelog

* chore: bump version to 1.0.6

* fix(orders): close missing brace in checkout shipping snapshot try block

* fix(checkout): correct nested try/catch structure for shipping snapshot

* fix(order-status): remove stale responseMode lite branch

---------

Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant