Merge pull request #138 from Ahbiz/feature/issue-132 unified saved hub and bookmark refactor (Closes #132) - #141
Conversation
feat(pwa): add offline support and installable PWA with Serwist
docs(readme): add live application screenshots
- Create `useEarnings` hook to aggregate creator transaction history into earnings analytics - Fetch all paginated creator-role transactions and separate confirmed, pending, submitted, failed, and expired states - Calculate total confirmed earnings, sales count, month-over-month comparison, and withdrawable USDC balance - Aggregate confirmed revenue into time-series data for analytics charts with 7-day, 30-day, and all-time range filters - Build per-item revenue and units-sold breakdown with links to related courses and books - Fetch creator courses and books to resolve item navigation within the dashboard - Add `/dashboard/earnings` page with: - Summary cards for total earnings, sales count, monthly trend, and withdrawable balance - Revenue-over-time chart using the shared chart components - Top-selling courses and books ranked by revenue - Transaction status breakdown with color-coded badges - Loading skeletons, empty state, error state with retry, and no-wallet state - Clear indication that only confirmed transactions contribute to earnings totals - Add an "Earnings" navigation item to the dashboard sidebar for quick access Implementation notes: - Reuses the existing Stellar transaction history and wallet infrastructure - Ensures earnings calculations include only confirmed transactions while exposing unsettled transaction statuses separately - Supports pagination-safe aggregation by fetching all creator transaction pages - Integrates with the existing chart wrapper and navigation structure without duplicating wallet transaction logic Verification: - Confirmed summary metrics, charts, and per-item analytics render correctly - Verified loading, empty, error, and no-wallet states - `npm run lint` passes successfully - `npm run build` introduces no new build issues (existing Firebase/Firestore build errors are unrelated to this implementation)
β¦d-enchancement ο»Ώfeat(dashboard): add educator earnings analytics dashboard
β¦ bookmark check
- Create `usePurchases` hook to aggregate owned courses and books with corresponding Stellar payment transactions
- Correlate purchased content using buyer-role transaction history while supporting pagination-safe aggregation
- Handle free enrollments gracefully by distinguishing unpaid content from paid purchases
- Expose reusable receipt lookup logic for retrieving confirmed transaction details per purchased item
- Add `/dashboard/purchases` page featuring:
- Separate Courses and Books tabs with owned item counts
- Responsive purchase cards with thumbnails, category badges, ownership status, pricing, and direct Watch/Read actions
- Purchase receipt modal displaying:
- USDC payment amount
- Transaction status
- Purchase date
- Creator information
- Truncated wallet addresses
- Stellar Explorer transaction link
- Printable browser-friendly receipt view
- "Free Enrollment" state for items without an associated on-chain transaction
- Loading skeletons, empty state with browse CTAs, and error state with retry support
- Add "My Purchases" navigation item to the dashboard sidebar with ShoppingBag icon
Implementation notes:
- Reuses existing purchase ownership data from `usePurchase`
- Integrates with Stellar transaction history instead of duplicating wallet transaction logic
- Reuses existing status color mappings and wallet address formatting utilities for UI consistency
- Supports all buyer transaction pages to ensure complete purchase history correlation
- Preserves existing dashboard architecture while introducing a dedicated content-centric purchase experience
Verification:
- Verified owned courses and books render correctly with appropriate Watch/Read actions
- Confirmed paid purchases display complete Stellar receipt details with working explorer links
- Validated free enrollments display a "Free Enrollment" state without broken receipts
- Confirmed loading, empty, retry, and error states function correctly
- `npm run lint` passes (pre-existing warnings only)
- `npm run build` introduces no new build issues (existing Firebase/Firestore issues are unrelated)
ο»Ώfeat(purchases): add My Purchases library with Stellar payment receipts
added prayer times and Hijri date widget to dashboard (Closes #133)
unified saved hub and bookmark refactor (Closes #132)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR adds saved, purchases, and earnings dashboard routes; a prayer-times widget; reusable bookmark state and controls; and PWA support with manifest metadata, install prompting, service-worker caching, offline navigation, and logout cache cleanup. ChangesDashboard feature additions
PWA and offline support
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Dashboard
participant PrayerTimesWidget
participant PrayerTimesService
participant AladhanAPI
Dashboard->>PrayerTimesWidget: Render widget
PrayerTimesWidget->>PrayerTimesService: Load location and timings
PrayerTimesService->>AladhanAPI: Request daily timings
AladhanAPI-->>PrayerTimesService: Return timings and dates
PrayerTimesService-->>PrayerTimesWidget: Return normalized data
PrayerTimesWidget-->>Dashboard: Render prayers and countdown
π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
π§Ή Nitpick comments (5)
app/dashboard/saved/page.jsx (1)
108-134: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winOptional: give the tab toggle proper tab semantics for screen readers.
These two buttons behave as a tab switcher, but to assistive tech they read as two independent buttons. Adding
role="tablist"on the container plusrole="tab"/aria-selectedon each button lets screen-reader users perceive the selected state and grouping. Keyboard operation already works since these are real<button>s, so this is a low-risk enhancement rather than a blocker.βΏ Suggested ARIA wiring
- <div className="flex gap-2 bg-background/60 p-1.5 rounded-full border shadow-sm"> + <div role="tablist" aria-label="Saved items" className="flex gap-2 bg-background/60 p-1.5 rounded-full border shadow-sm"> <button type="button" + role="tab" + aria-selected={activeTab === "courses"} onClick={() => setActiveTab("courses")}Apply the same
role="tab"/aria-selectedto the Books button.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/dashboard/saved/page.jsx` around lines 108 - 134, Add tab semantics to the active tab switcher: mark the wrapping container as a tablist and apply role="tab" with aria-selected reflecting activeTab to both the Courses and Books buttons. Preserve the existing button behavior and styling.components/organisms/dashboard/PrayerTimesWidget.jsx (1)
148-167: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winPrefer inline error UI over blocking
alert()for GPS failures.
alert()blocks the main thread and is jarring/inconsistent with the rest of the app's styled feedback (e.g., theerrorstate block used elsewhere in this same component). Consider setting a small inline message near the "Use Auto GPS" button instead.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/organisms/dashboard/PrayerTimesWidget.jsx` around lines 148 - 167, Update the GPS failure callback in handleAutoDetectGPS to set the componentβs existing error state instead of calling alert(). Render or reuse the established inline error UI near the βUse Auto GPSβ button, preserving the current geolocation success flow.app/offline/page.jsx (1)
9-21: π― Functional Correctness | π΅ Trivial | β‘ Quick winHide decorative icon from assistive tech.
This
<svg>is purely decorative (the heading already conveys "You're Offline"). Withoutaria-hidden="true", some screen readers may announce it as an unlabeled graphic.βΏ Proposed fix
<svg className="h-10 w-10 text-accent" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5} + aria-hidden="true" >π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/offline/page.jsx` around lines 9 - 21, Add aria-hidden="true" to the decorative svg element in the offline page, leaving its visual styling and existing path content unchanged so assistive technologies skip it.Source: Path instructions
app/sw.js (1)
117-130: π― Functional Correctness | π΅ Trivial | π€ Low valueHTML/navigation detection via
Content-Typerequest header is unreliable.GET navigation requests don't carry a
Content-Typerequest header (it describes the body's media type; navigations have no body). Browsers instead expose navigation intent viaRequest.mode === "navigate"(surfaced server-side asSec-Fetch-Mode: navigate). As written, this matcher will likely never match real page navigations, so they silently fall through to thesame-origin-othersbucket below instead ofpages.Current impact is limited since both buckets share identical
maxEntries/maxAgeSeconds, but if you ever tune them differently the "pages" cache would stay empty.β»οΈ Proposed fix
{ - matcher: ({ request, url, sameOrigin }) => - request.headers.get("Content-Type")?.includes("text/html") && - sameOrigin, + matcher: ({ request, sameOrigin }) => + sameOrigin && request.mode === "navigate", handler: new NetworkFirst({ cacheName: "pages",π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/sw.js` around lines 117 - 130, Update the matcher associated with the NetworkFirst βpagesβ cache to identify same-origin navigations using request.mode === "navigate" instead of inspecting the request Content-Type header. Preserve the existing sameOrigin requirement and leave the cache configuration unchanged.public/sw.js (1)
1-1: π Maintainability & Code Quality | π΅ Trivial | β‘ Quick winGenerated Serwist build artifacts checked into git, with evidence of a Windows-only path bug. Both files are output of the
withSerwistInit({ swSrc: "app/sw.js", swDest: "public/sw.js", ... })pipeline innext.config.mjsand get fully regenerated on every production build β they shouldn't need to be committed. The precache manifest baked intopublic/sw.jscontains backslash-separated URLs (e.g./icons\icon-192x192-maskable.png), which is a giveaway that this specific commit was generated on a Windows machine; if this stale copy is ever served without a fresh build (partial deploys, CDN edge cases, etc.), those asset paths are invalid and precaching would fail for them.
public/sw.js#L1-L1: add this generated file to.gitignoreand let CI/build regenerate it on every deploy instead of committing it.public/swe-worker-f61931bc2770d10b.js#L1-L1: same β gitignore this Serwist-generated worker chunk rather than committing the build output.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@public/sw.js` at line 1, Generated Serwist service-worker artifacts are committed and contain machine-specific precache paths; ignore both generated outputs and remove them from version control. Update .gitignore to exclude public/sw.js and public/swe-worker-f61931bc2770d10b.js, with no direct source changes required in either generated file; ensure the withSerwistInit build regenerates them during deployment.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/account/settings/page.jsx`:
- Around line 62-70: Update handleInstall so installable is set to false after
any userChoice outcome, not only when the outcome is "accepted"; continue
clearing deferredPrompt afterward so dismissing the native prompt cannot leave
the banner visible with no deferred prompt.
In `@app/layout.js`:
- Line 21: Update the layout metadata manifest value from β/manifestβ to
β/manifest.webmanifestβ so it references the route served by app/manifest.js and
preserves valid PWA installation metadata.
In `@components/organisms/dashboard/PrayerTimesWidget.jsx`:
- Around line 199-208: Update the loading skeleton grid in PrayerTimesWidget to
use the same responsive column classes as the rendered prayer cards: two columns
by default, three at sm, and five at md. Keep the five skeleton items and
existing styling unchanged.
- Around line 49-97: Guard the initial-load effect in the component containing
loadData so its initialization logic executes only once, preventing
stored-location updates from retriggering it through the loadData dependency.
Preserve the existing stored-location, geolocation, and fallback selection
behavior, while keeping loadDataβs location-dependent behavior unchanged.
- Around line 49-65: Update the loadData callback in PrayerTimesWidget so any
response with isFallback: true sets the error state instead of being accepted by
setData, regardless of whether activeLocation exists. Preserve the existing
handling for valid responses and thrown errors, and avoid rendering fallback
prayer times as successful location-specific data.
In `@hooks/useAuth.js`:
- Around line 39-49: Update the logout cache-cleanup logic in the shown
window/caches block to delete all runtime caches, including explicitly named
caches such as "pages", instead of relying only on the "serwist-" prefix and
"book-previews" match. Preserve the existing asynchronous cleanup flow and
ensure every current service-worker runtime cache is removed on logout.
In `@hooks/useEarnings.js`:
- Around line 135-152: Update the last-month filter in the useMemo revenue
calculation to include transactions before startOfThisMonth, removing the
endOfLastMonth dependency from getLastMonthRange and its destructuring. Preserve
the existing startOfLastMonth lower bound and month-over-month calculation.
In `@lib/services/prayer-times.js`:
- Around line 90-124: Update the coordinate checks used to build locationKey and
select the API request in the surrounding prayer-times flow so valid zero
latitude or longitude values are accepted. Test coordinate presence explicitly
rather than relying on truthiness, while preserving the existing city/default
fallback behavior when either coordinate is absent.
- Around line 126-129: Update the Aladhan fetch flow in the surrounding
prayer-times function to use an AbortController with a finite timeout, pass its
signal to fetch, and clear the timeout after completion. Ensure timeout or abort
errors follow the existing catch/fallback path so loading cannot remain stuck on
a hanging request.
- Around line 213-224: Update getNextPrayerInfo to stop converting the
locale-formatted string with new Date(tzStr), which is engine-dependent and can
produce Invalid Date. Derive the timezone-adjusted date using a timezone-safe
Intl.DateTimeFormat(...).formatToParts approach or the projectβs existing
timezone helper, while preserving the current fallback to the local Date when
timezone handling fails.
In `@next.config.mjs`:
- Around line 3-9: Update the withSerwist configuration to set reloadOnOnline to
false, preventing reconnect-triggered reloads while preserving the other
service-worker options.
In `@package.json`:
- Line 41: Add serwist as a direct devDependency in package.json alongside
`@serwist/next`, so the app/sw.js import resolves from an explicitly declared
package during clean installs.
---
Nitpick comments:
In `@app/dashboard/saved/page.jsx`:
- Around line 108-134: Add tab semantics to the active tab switcher: mark the
wrapping container as a tablist and apply role="tab" with aria-selected
reflecting activeTab to both the Courses and Books buttons. Preserve the
existing button behavior and styling.
In `@app/offline/page.jsx`:
- Around line 9-21: Add aria-hidden="true" to the decorative svg element in the
offline page, leaving its visual styling and existing path content unchanged so
assistive technologies skip it.
In `@app/sw.js`:
- Around line 117-130: Update the matcher associated with the NetworkFirst
βpagesβ cache to identify same-origin navigations using request.mode ===
"navigate" instead of inspecting the request Content-Type header. Preserve the
existing sameOrigin requirement and leave the cache configuration unchanged.
In `@components/organisms/dashboard/PrayerTimesWidget.jsx`:
- Around line 148-167: Update the GPS failure callback in handleAutoDetectGPS to
set the componentβs existing error state instead of calling alert(). Render or
reuse the established inline error UI near the βUse Auto GPSβ button, preserving
the current geolocation success flow.
In `@public/sw.js`:
- Line 1: Generated Serwist service-worker artifacts are committed and contain
machine-specific precache paths; ignore both generated outputs and remove them
from version control. Update .gitignore to exclude public/sw.js and
public/swe-worker-f61931bc2770d10b.js, with no direct source changes required in
either generated file; ensure the withSerwistInit build regenerates them during
deployment.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 873d34aa-9fec-43c2-8989-d3fbe82ed011
β Files ignored due to path filters (11)
docs/screenshots/courses.pngis excluded by!**/*.pngdocs/screenshots/dashboard.pngis excluded by!**/*.pngdocs/screenshots/landing.pngis excluded by!**/*.pngdocs/screenshots/library.pngis excluded by!**/*.pngdocs/screenshots/login.pngis excluded by!**/*.pngdocs/screenshots/wallet.pngis excluded by!**/*.pngpackage-lock.jsonis excluded by!**/package-lock.jsonpublic/icons/icon-192x192-maskable.pngis excluded by!**/*.pngpublic/icons/icon-192x192.pngis excluded by!**/*.pngpublic/icons/icon-512x512-maskable.pngis excluded by!**/*.pngpublic/icons/icon-512x512.pngis excluded by!**/*.png
π Files selected for processing (27)
.npmrcREADME.mdapp/account/settings/page.jsxapp/dashboard/earnings/page.jsxapp/dashboard/page.jsxapp/dashboard/purchases/page.jsxapp/dashboard/saved/page.jsxapp/layout.jsapp/manifest.jsapp/offline/page.jsxapp/sw.jscomponents/atoms/BookmarkButton.jsxcomponents/molecules/dashboard/cards/courseCard.jsxcomponents/molecules/dashboard/cards/libraryCard.jsxcomponents/molecules/dashboard/nav-routers.jsxcomponents/organisms/dashboard/PrayerTimesWidget.jsxhooks/useAuth.jshooks/useBookBookmark.jshooks/useBookmark.jshooks/useBookmarkCore.jshooks/useEarnings.jshooks/usePurchases.jslib/services/prayer-times.jsnext.config.mjspackage.jsonpublic/sw.jspublic/swe-worker-f61931bc2770d10b.js
| const handleInstall = async () => { | ||
| if (!deferredPrompt) return; | ||
| deferredPrompt.prompt(); | ||
| const result = await deferredPrompt.userChoice; | ||
| if (result.outcome === "accepted") { | ||
| setInstallable(false); | ||
| } | ||
| setDeferredPrompt(null); | ||
| }; |
There was a problem hiding this comment.
π― Functional Correctness | π‘ Minor | β‘ Quick win
Install banner becomes a dead end after the user dismisses the native prompt.
installable only resets to false on "accepted". On dismiss, deferredPrompt is cleared but the banner (which gates on installable) stays visible β clicking "Install" again then silently no-ops since handleInstall early-returns when deferredPrompt is null.
π Proposed fix
const handleInstall = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const result = await deferredPrompt.userChoice;
- if (result.outcome === "accepted") {
- setInstallable(false);
- }
+ setInstallable(false);
setDeferredPrompt(null);
};π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleInstall = async () => { | |
| if (!deferredPrompt) return; | |
| deferredPrompt.prompt(); | |
| const result = await deferredPrompt.userChoice; | |
| if (result.outcome === "accepted") { | |
| setInstallable(false); | |
| } | |
| setDeferredPrompt(null); | |
| }; | |
| const handleInstall = async () => { | |
| if (!deferredPrompt) return; | |
| deferredPrompt.prompt(); | |
| const result = await deferredPrompt.userChoice; | |
| setInstallable(false); | |
| setDeferredPrompt(null); | |
| }; |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/account/settings/page.jsx` around lines 62 - 70, Update handleInstall so
installable is set to false after any userChoice outcome, not only when the
outcome is "accepted"; continue clearing deferredPrompt afterward so dismissing
the native prompt cannot leave the banner visible with no deferred prompt.
| title: "Deen Bridge", | ||
| description: | ||
| "Empowering Muslims with authentic knowledge β Learn Qur'an, Arabic, Fiqh, and more through 1-on-1 live mentorship and lots more.", | ||
| manifest: "/manifest", |
There was a problem hiding this comment.
π― Functional Correctness | π Major | β‘ Quick win
π§© Analysis chain
π Script executed:
#!/bin/bash
set -euo pipefail
printf 'Files of interest:\n'
git ls-files app/layout.js app/manifest.js public/sw.js 2>/dev/null || true
printf '\nLayout snippet:\n'
sed -n '1,120p' app/layout.js 2>/dev/null || true
printf '\nManifest file:\n'
sed -n '1,120p' app/manifest.js 2>/dev/null || true
printf '\nReferences to manifest path:\n'
rg -n 'manifest\.webmanifest|manifest: "/manifest"|manifest: "/manifest\.webmanifest"|/manifest"' app public 2>/dev/null || trueRepository: Deen-Bridge/dnb-frontend
Length of output: 34411
Point manifest at /manifest.webmanifest
app/manifest.js is served at /manifest.webmanifest; manifest: "/manifest" emits a broken link and can block PWA install.
π Proposed fix
- manifest: "/manifest",
+ manifest: "/manifest.webmanifest",π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| manifest: "/manifest", | |
| manifest: "/manifest.webmanifest", |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/layout.js` at line 21, Update the layout metadata manifest value from
β/manifestβ to β/manifest.webmanifestβ so it references the route served by
app/manifest.js and preserves valid PWA installation metadata.
| const loadData = useCallback(async (locObj) => { | ||
| setLoading(true); | ||
| setError(false); | ||
| try { | ||
| const activeLocation = locObj || location || (user?.country ? { city: user.country } : null); | ||
| const res = await fetchPrayerTimes(activeLocation); | ||
| if (!res || (res.isFallback && !activeLocation)) { | ||
| setError(true); | ||
| } else { | ||
| setData(res); | ||
| } | ||
| } catch (_err) { | ||
| setError(true); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }, [location, user?.country]); |
There was a problem hiding this comment.
π― Functional Correctness | π Major | β‘ Quick win
isFallback is silently swallowed once a location is set, hiding real API failures.
setError(true) only fires when res.isFallback && !activeLocation. Once the user has a real activeLocation (stored, geolocated, or manually entered) and the Aladhan request fails for any reason (network blip, bad city name, API outage), fetchPrayerTimes (Line 181-204 in lib/services/prayer-times.js) still returns a resolved object with isFallback: true and generic hardcoded times β and this branch happily calls setData(res), rendering those made-up times as if they were the user's real, location-specific prayer schedule with no indication anything went wrong.
π§ Proposed fix β surface the fallback state instead of discarding it
const res = await fetchPrayerTimes(activeLocation);
- if (!res || (res.isFallback && !activeLocation)) {
- setError(true);
- } else {
- setData(res);
- }
+ if (!res) {
+ setError(true);
+ } else {
+ setData(res);
+ // consider surfacing res.isFallback in the UI (e.g. a subtle "estimated times" badge)
+ }π§° Tools
πͺ ast-grep (0.44.1)
[warning] 57-57: Avoid using the initial state variable in setState
Context: setData(res)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/organisms/dashboard/PrayerTimesWidget.jsx` around lines 49 - 65,
Update the loadData callback in PrayerTimesWidget so any response with
isFallback: true sets the error state instead of being accepted by setData,
regardless of whether activeLocation exists. Preserve the existing handling for
valid responses and thrown errors, and avoid rendering fallback prayer times as
successful location-specific data.
| const loadData = useCallback(async (locObj) => { | ||
| setLoading(true); | ||
| setError(false); | ||
| try { | ||
| const activeLocation = locObj || location || (user?.country ? { city: user.country } : null); | ||
| const res = await fetchPrayerTimes(activeLocation); | ||
| if (!res || (res.isFallback && !activeLocation)) { | ||
| setError(true); | ||
| } else { | ||
| setData(res); | ||
| } | ||
| } catch (_err) { | ||
| setError(true); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }, [location, user?.country]); | ||
|
|
||
| // Initial load & Geolocation auto-detection | ||
| useEffect(() => { | ||
| const stored = getStoredLocation(); | ||
| if (stored) { | ||
| setLocation(stored); | ||
| loadData(stored); | ||
| } else if (typeof window !== "undefined" && navigator.geolocation) { | ||
| // Try non-blocking geolocation | ||
| navigator.geolocation.getCurrentPosition( | ||
| (pos) => { | ||
| const geoLoc = { | ||
| lat: pos.coords.latitude, | ||
| lng: pos.coords.longitude, | ||
| name: "Current Location", | ||
| }; | ||
| setLocation(geoLoc); | ||
| setStoredLocation(geoLoc); | ||
| loadData(geoLoc); | ||
| }, | ||
| (_geoErr) => { | ||
| // Fallback to profile country or default | ||
| const fallbackLoc = user?.country ? { city: user.country } : null; | ||
| loadData(fallbackLoc); | ||
| }, | ||
| { timeout: 5000 } | ||
| ); | ||
| } else { | ||
| const fallbackLoc = user?.country ? { city: user.country } : null; | ||
| loadData(fallbackLoc); | ||
| } | ||
| }, [user?.country, loadData]); |
There was a problem hiding this comment.
π©Ί Stability & Availability | π΄ Critical | β‘ Quick win
Infinite effect loop when a stored location exists.
getStoredLocation() (in lib/services/prayer-times.js) parses JSON on every call, returning a brand-new object reference each time even when the content is identical. Here's the loop this creates:
- Effect runs β
setLocation(stored)with a fresh object βlocationstate reference changes. loadDatais auseCallbackwithlocationin its deps (Line 65) β its identity changes too.- The effect's dep array
[user?.country, loadData]now differs β effect re-runs. - Repeat forever.
Any user with a previously saved location (the common case after first use) will trigger continuous re-renders and repeated loadData calls indefinitely β this is a genuine runaway effect, not just a wasted render.
π Proposed fix β guard the initial-load effect so it only runs its body once
+ const didInitRef = useRef(false);
+
// Initial load & Geolocation auto-detection
useEffect(() => {
+ if (didInitRef.current) return;
+ didInitRef.current = true;
const stored = getStoredLocation();
if (stored) {
setLocation(stored);
loadData(stored);
} else if (typeof window !== "undefined" && navigator.geolocation) {π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const loadData = useCallback(async (locObj) => { | |
| setLoading(true); | |
| setError(false); | |
| try { | |
| const activeLocation = locObj || location || (user?.country ? { city: user.country } : null); | |
| const res = await fetchPrayerTimes(activeLocation); | |
| if (!res || (res.isFallback && !activeLocation)) { | |
| setError(true); | |
| } else { | |
| setData(res); | |
| } | |
| } catch (_err) { | |
| setError(true); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [location, user?.country]); | |
| // Initial load & Geolocation auto-detection | |
| useEffect(() => { | |
| const stored = getStoredLocation(); | |
| if (stored) { | |
| setLocation(stored); | |
| loadData(stored); | |
| } else if (typeof window !== "undefined" && navigator.geolocation) { | |
| // Try non-blocking geolocation | |
| navigator.geolocation.getCurrentPosition( | |
| (pos) => { | |
| const geoLoc = { | |
| lat: pos.coords.latitude, | |
| lng: pos.coords.longitude, | |
| name: "Current Location", | |
| }; | |
| setLocation(geoLoc); | |
| setStoredLocation(geoLoc); | |
| loadData(geoLoc); | |
| }, | |
| (_geoErr) => { | |
| // Fallback to profile country or default | |
| const fallbackLoc = user?.country ? { city: user.country } : null; | |
| loadData(fallbackLoc); | |
| }, | |
| { timeout: 5000 } | |
| ); | |
| } else { | |
| const fallbackLoc = user?.country ? { city: user.country } : null; | |
| loadData(fallbackLoc); | |
| } | |
| }, [user?.country, loadData]); | |
| const didInitRef = useRef(false); | |
| // Initial load & Geolocation auto-detection | |
| useEffect(() => { | |
| if (didInitRef.current) return; | |
| didInitRef.current = true; | |
| const stored = getStoredLocation(); | |
| if (stored) { | |
| setLocation(stored); | |
| loadData(stored); | |
| } else if (typeof window !== "undefined" && navigator.geolocation) { | |
| // Try non-blocking geolocation | |
| navigator.geolocation.getCurrentPosition( | |
| (pos) => { | |
| const geoLoc = { | |
| lat: pos.coords.latitude, | |
| lng: pos.coords.longitude, | |
| name: "Current Location", | |
| }; | |
| setLocation(geoLoc); | |
| setStoredLocation(geoLoc); | |
| loadData(geoLoc); | |
| }, | |
| (_geoErr) => { | |
| // Fallback to profile country or default | |
| const fallbackLoc = user?.country ? { city: user.country } : null; | |
| loadData(fallbackLoc); | |
| }, | |
| { timeout: 5000 } | |
| ); | |
| } else { | |
| const fallbackLoc = user?.country ? { city: user.country } : null; | |
| loadData(fallbackLoc); | |
| } | |
| }, [user?.country, loadData]); |
π§° Tools
πͺ ast-grep (0.44.1)
[warning] 57-57: Avoid using the initial state variable in setState
Context: setData(res)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 70-70: Avoid using the initial state variable in setState
Context: setLocation(stored)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 81-81: Avoid using the initial state variable in setState
Context: setLocation(geoLoc)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 82-82: Avoid using the initial state variable in setState
Context: setStoredLocation(geoLoc)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/organisms/dashboard/PrayerTimesWidget.jsx` around lines 49 - 97,
Guard the initial-load effect in the component containing loadData so its
initialization logic executes only once, preventing stored-location updates from
retriggering it through the loadData dependency. Preserve the existing
stored-location, geolocation, and fallback selection behavior, while keeping
loadDataβs location-dependent behavior unchanged.
| {/* Loading State */} | ||
| {loading ? ( | ||
| <div className="animate-pulse space-y-3 py-4"> | ||
| <div className="h-16 bg-muted rounded-xl w-full" /> | ||
| <div className="grid grid-cols-5 gap-2"> | ||
| {[...Array(5)].map((_, i) => ( | ||
| <div key={`skel-pr-${i}`} className="h-20 bg-muted rounded-xl" /> | ||
| ))} | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
Loading skeleton column count doesn't match the real content's responsive breakpoints.
The skeleton grid is hardcoded to grid-cols-5 (Line 203) while the actual prayer cards (Line 251) use grid-cols-2 sm:grid-cols-3 md:grid-cols-5. On mobile this causes a visible layout shift/jump when loading finishes.
π¨ Proposed fix
- <div className="grid grid-cols-5 gap-2">
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2">π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* Loading State */} | |
| {loading ? ( | |
| <div className="animate-pulse space-y-3 py-4"> | |
| <div className="h-16 bg-muted rounded-xl w-full" /> | |
| <div className="grid grid-cols-5 gap-2"> | |
| {[...Array(5)].map((_, i) => ( | |
| <div key={`skel-pr-${i}`} className="h-20 bg-muted rounded-xl" /> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Loading State */} | |
| {loading ? ( | |
| <div className="animate-pulse space-y-3 py-4"> | |
| <div className="h-16 bg-muted rounded-xl w-full" /> | |
| <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2"> | |
| {[...Array(5)].map((_, i) => ( | |
| <div key={`skel-pr-${i}`} className="h-20 bg-muted rounded-xl" /> | |
| ))} | |
| </div> | |
| </div> |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/organisms/dashboard/PrayerTimesWidget.jsx` around lines 199 - 208,
Update the loading skeleton grid in PrayerTimesWidget to use the same responsive
column classes as the rendered prayer cards: two columns by default, three at
sm, and five at md. Keep the five skeleton items and existing styling unchanged.
| const locationKey = location?.lat && location?.lng | ||
| ? `${location.lat.toFixed(2)},${location.lng.toFixed(2)}` | ||
| : (location?.city || location?.name || "default"); | ||
|
|
||
| // Check cache | ||
| if (typeof window !== "undefined") { | ||
| try { | ||
| const cached = localStorage.getItem(CACHE_KEY); | ||
| if (cached) { | ||
| const parsed = JSON.parse(cached); | ||
| if (parsed.dateKey === todayStr && parsed.locationKey === locationKey && parsed.data) { | ||
| return parsed.data; | ||
| } | ||
| } | ||
| } catch (_e) { | ||
| // Ignore cache read errors | ||
| } | ||
| } | ||
|
|
||
| let apiUrl = ""; | ||
| let locationName = "Current Location"; | ||
|
|
||
| if (location?.lat && location?.lng) { | ||
| const timestamp = Math.floor(Date.now() / 1000); | ||
| apiUrl = `https://api.aladhan.com/v1/timings/${timestamp}?latitude=${location.lat}&longitude=${location.lng}&method=2`; | ||
| locationName = location.name || `${location.lat.toFixed(2)}Β°, ${location.lng.toFixed(2)}Β°`; | ||
| } else if (location?.city) { | ||
| const country = location.country || ""; | ||
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent(location.city)}&country=${encodeURIComponent(country)}&method=2`; | ||
| locationName = location.country ? `${location.city}, ${location.country}` : location.city; | ||
| } else { | ||
| // Default fallback city (Mecca / Saudi Arabia) | ||
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent("Mecca")}&country=${encodeURIComponent("Saudi Arabia")}&method=2`; | ||
| locationName = "Mecca, Saudi Arabia"; | ||
| } |
There was a problem hiding this comment.
π― Functional Correctness | π Major | β‘ Quick win
Falsy-check bug: lat/lng of 0 silently discards valid GPS coordinates.
location?.lat && location?.lng (Lines 90, 112) treats an exact 0 value as falsy. A user located on the equator (lat: 0) or on the prime meridian (lng: 0) will fall through to the city/default branch, silently losing their real GPS-based location for both the cache key and the API request built later.
π Proposed fix
- const locationKey = location?.lat && location?.lng
+ const hasCoords = typeof location?.lat === "number" && typeof location?.lng === "number";
+ const locationKey = hasCoords
? `${location.lat.toFixed(2)},${location.lng.toFixed(2)}`
: (location?.city || location?.name || "default");- if (location?.lat && location?.lng) {
+ if (hasCoords) {π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const locationKey = location?.lat && location?.lng | |
| ? `${location.lat.toFixed(2)},${location.lng.toFixed(2)}` | |
| : (location?.city || location?.name || "default"); | |
| // Check cache | |
| if (typeof window !== "undefined") { | |
| try { | |
| const cached = localStorage.getItem(CACHE_KEY); | |
| if (cached) { | |
| const parsed = JSON.parse(cached); | |
| if (parsed.dateKey === todayStr && parsed.locationKey === locationKey && parsed.data) { | |
| return parsed.data; | |
| } | |
| } | |
| } catch (_e) { | |
| // Ignore cache read errors | |
| } | |
| } | |
| let apiUrl = ""; | |
| let locationName = "Current Location"; | |
| if (location?.lat && location?.lng) { | |
| const timestamp = Math.floor(Date.now() / 1000); | |
| apiUrl = `https://api.aladhan.com/v1/timings/${timestamp}?latitude=${location.lat}&longitude=${location.lng}&method=2`; | |
| locationName = location.name || `${location.lat.toFixed(2)}Β°, ${location.lng.toFixed(2)}Β°`; | |
| } else if (location?.city) { | |
| const country = location.country || ""; | |
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent(location.city)}&country=${encodeURIComponent(country)}&method=2`; | |
| locationName = location.country ? `${location.city}, ${location.country}` : location.city; | |
| } else { | |
| // Default fallback city (Mecca / Saudi Arabia) | |
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent("Mecca")}&country=${encodeURIComponent("Saudi Arabia")}&method=2`; | |
| locationName = "Mecca, Saudi Arabia"; | |
| } | |
| const hasCoords = typeof location?.lat === "number" && typeof location?.lng === "number"; | |
| const locationKey = hasCoords | |
| ? `${location.lat.toFixed(2)},${location.lng.toFixed(2)}` | |
| : (location?.city || location?.name || "default"); | |
| // Check cache | |
| if (typeof window !== "undefined") { | |
| try { | |
| const cached = localStorage.getItem(CACHE_KEY); | |
| if (cached) { | |
| const parsed = JSON.parse(cached); | |
| if (parsed.dateKey === todayStr && parsed.locationKey === locationKey && parsed.data) { | |
| return parsed.data; | |
| } | |
| } | |
| } catch (_e) { | |
| // Ignore cache read errors | |
| } | |
| } | |
| let apiUrl = ""; | |
| let locationName = "Current Location"; | |
| if (hasCoords) { | |
| const timestamp = Math.floor(Date.now() / 1000); | |
| apiUrl = `https://api.aladhan.com/v1/timings/${timestamp}?latitude=${location.lat}&longitude=${location.lng}&method=2`; | |
| locationName = location.name || `${location.lat.toFixed(2)}Β°, ${location.lng.toFixed(2)}Β°`; | |
| } else if (location?.city) { | |
| const country = location.country || ""; | |
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent(location.city)}&country=${encodeURIComponent(country)}&method=2`; | |
| locationName = location.country ? `${location.city}, ${location.country}` : location.city; | |
| } else { | |
| // Default fallback city (Mecca / Saudi Arabia) | |
| apiUrl = `https://api.aladhan.com/v1/timingsByCity?city=${encodeURIComponent("Mecca")}&country=${encodeURIComponent("Saudi Arabia")}&method=2`; | |
| locationName = "Mecca, Saudi Arabia"; | |
| } |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/services/prayer-times.js` around lines 90 - 124, Update the coordinate
checks used to build locationKey and select the API request in the surrounding
prayer-times flow so valid zero latitude or longitude values are accepted. Test
coordinate presence explicitly rather than relying on truthiness, while
preserving the existing city/default fallback behavior when either coordinate is
absent.
| try { | ||
| const res = await fetch(apiUrl); | ||
| if (!res.ok) throw new Error("Failed to fetch prayer times from API"); | ||
| const json = await res.json(); |
There was a problem hiding this comment.
π©Ί Stability & Availability | π Major | β‘ Quick win
Add a timeout/abort guard around the Aladhan fetch.
There's no AbortController/timeout here, so a slow or hanging upstream response can leave the widget's loading state stuck far longer than a user will tolerate, with no way to bail out early into the fallback path.
π§ Proposed fix
try {
- const res = await fetch(apiUrl);
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 8000);
+ const res = await fetch(apiUrl, { signal: controller.signal }).finally(() =>
+ clearTimeout(timeoutId)
+ );
if (!res.ok) throw new Error("Failed to fetch prayer times from API");π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| const res = await fetch(apiUrl); | |
| if (!res.ok) throw new Error("Failed to fetch prayer times from API"); | |
| const json = await res.json(); | |
| try { | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 8000); | |
| const res = await fetch(apiUrl, { signal: controller.signal }).finally(() => | |
| clearTimeout(timeoutId) | |
| ); | |
| if (!res.ok) throw new Error("Failed to fetch prayer times from API"); | |
| const json = await res.json(); |
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/services/prayer-times.js` around lines 126 - 129, Update the Aladhan
fetch flow in the surrounding prayer-times function to use an AbortController
with a finite timeout, pass its signal to fetch, and clear the timeout after
completion. Ensure timeout or abort errors follow the existing catch/fallback
path so loading cannot remain stuck on a hanging request.
| export function getNextPrayerInfo(timings, timezone = null) { | ||
| if (!timings) return { nextPrayerName: "Fajr", remainingSeconds: 0, isTomorrow: false }; | ||
|
|
||
| let nowInTz = new Date(); | ||
| if (timezone) { | ||
| try { | ||
| const tzStr = new Date().toLocaleString("en-US", { timeZone: timezone }); | ||
| nowInTz = new Date(tzStr); | ||
| } catch (_e) { | ||
| nowInTz = new Date(); | ||
| } | ||
| } |
There was a problem hiding this comment.
π― Functional Correctness | π‘ Minor | β‘ Quick win
π§© Analysis chain
π Web query:
Is parsing the output of Date.prototype.toLocaleString back into a Date object officially specified or safe in modern JS engines?
π‘ Result:
No, parsing the output of Date.prototype.toLocaleString back into a Date object is not officially specified as safe or reliable in modern JavaScript [1][2][3]. The ECMAScript specification does not guarantee that toLocaleString produces a format that Date.parse or the Date constructor can reliably interpret [1][2][3]. The output of toLocaleString is intentionally implementation-defined to allow for locale-specific formatting variationsβsuch as different date/time orderings, specialized calendar symbols, non-breaking spaces, or bidirectional control charactersβwhich are not standardized and can vary between browsers, operating systems, and even different versions of the same environment [4][5]. Key points regarding this practice: - Implementation-Defined Behavior: The specification explicitly states that while Date.parse may attempt to support various formats, it is not required to handle the strings produced by toLocaleString [1][2][3]. - Lack of Round-trip Guarantee: Unlike toISOString, which is explicitly designed for machine-readable, round-trip serialization of date objects, toLocaleString is intended solely for human-readable display [4][3]. - Lack of Parsing Facilities: The TC39 committee has intentionally excluded parsing facilities from the Internationalization API (ECMA-402) after extensive deliberation, noting that reliable date parsing is inherently complex and prone to ambiguity [6][7]. If you require a format that can be safely parsed back into a Date object, you should use toISOString or a structured, standardized format that is explicitly supported by Date.parse, such as the ISO 8601 subset defined in the ECMAScript specification [1][3].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Date/parse
- 2: https://stackoverflow.com/questions/29988868/why-does-parsing-a-locale-date-string-result-in-an-invalid-date
- 3: Error Date.toLocaleString() in node 19.0.0Β nodejs/node#45171
- 4: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
- 5: https://stackoverflow.com/questions/26867970/ie-10-unable-to-parse-the-date-strings-produced-with-its-own-date-prototype-tol
- 6: https://tc39.es/ecma402/
- 7: https://www.ecma-international.org/wp-content/uploads/ECMA-402_11th_edition_june_2024.pdf
π Script executed:
#!/bin/bash
set -euo pipefail
git ls-files lib/services/prayer-times.js
wc -l lib/services/prayer-times.js
cat -n lib/services/prayer-times.js | sed -n '190,245p'Repository: Deen-Bridge/dnb-frontend
Length of output: 2366
Avoid parsing toLocaleString() output here. new Date(tzStr) depends on engine-specific date parsing and can become Invalid Date, which turns the remaining-time math into NaN. Use a timezone-safe approach instead, such as Intl.DateTimeFormat(...).formatToParts or a dedicated timezone helper.
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/services/prayer-times.js` around lines 213 - 224, Update
getNextPrayerInfo to stop converting the locale-formatted string with new
Date(tzStr), which is engine-dependent and can produce Invalid Date. Derive the
timezone-adjusted date using a timezone-safe
Intl.DateTimeFormat(...).formatToParts approach or the projectβs existing
timezone helper, while preserving the current fallback to the local Date when
timezone handling fails.
| const withSerwist = withSerwistInit({ | ||
| swSrc: "app/sw.js", | ||
| swDest: "public/sw.js", | ||
| disable: process.env.NODE_ENV === "development", | ||
| cacheOnNavigation: true, | ||
| reloadOnOnline: true, | ||
| }); |
There was a problem hiding this comment.
π©Ί Stability & Availability | π Major | β‘ Quick win
π§© Analysis chain
π Script executed:
#!/bin/bash
set -euo pipefail
echo "== next.config.mjs =="
cat -n next.config.mjs
echo
echo "== Search for form-related surfaces =="
rg -n --hidden -S "autosave|save on|unsaved|form|textarea|input|messag|profile|settings" app src pages components . \
-g '!node_modules' -g '!dist' -g '!build' -g '!coverage' || trueRepository: Deen-Bridge/dnb-frontend
Length of output: 50381
π Script executed:
#!/bin/bash
set -euo pipefail
echo "== next.config.mjs =="
cat -n next.config.mjs
echo
echo "== Candidate editor/form files =="
git ls-files \
"app/**/settings*" \
"app/**/profile*" \
"app/**/messages*" \
"components/**/settings*" \
"components/**/profile*" \
"components/**/messages*" \
"components/**/form*" \
| sed -n '1,200p'
echo
echo "== Search for autosave / draft persistence / unload handling =="
rg -n -S "autosave|auto-save|draft|persist|localStorage|sessionStorage|beforeunload|visibilitychange|ononline|reloadOnOnline|save.*draft|keep.*draft" \
app components hooks lib \
-g '!node_modules' -g '!dist' -g '!build' -g '!coverage' || trueRepository: Deen-Bridge/dnb-frontend
Length of output: 5230
π Script executed:
#!/bin/bash
set -euo pipefail
echo "== outlines =="
ast-grep outline app/account/settings/page.jsx --view expanded || true
echo
ast-grep outline app/account/profile/[profileid]/page.jsx --view expanded || true
echo
ast-grep outline app/dashboard/messages/[room]/page.jsx --view expanded || true
echo
ast-grep outline components/organisms/settings/resetPassword.jsx --view expanded || true
echo
ast-grep outline components/organisms/account/profile/ProfileContent.jsx --view expanded || true
echo
ast-grep outline components/organisms/account/profile/ProfileTabs.jsx --view expanded || true
echo
ast-grep outline components/molecules/messages/isTypingbubbles.jsx --view expanded || true
echo
echo "== settings page excerpt =="
cat -n app/account/settings/page.jsx | sed -n '1,220p'
echo
echo "== profile page excerpt =="
cat -n app/account/profile/[profileid]/page.jsx | sed -n '1,220p'
echo
echo "== messages room page excerpt =="
cat -n app/dashboard/messages/[room]/page.jsx | sed -n '1,220p'Repository: Deen-Bridge/dnb-frontend
Length of output: 20891
Disable reconnect reloads in next.config.mjs:3-8. reloadOnOnline: true can wipe unsaved settings, profile, and message drafts when a user goes offline and comes back online. Flip it to false unless discarding in-progress edits is intentional.
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@next.config.mjs` around lines 3 - 9, Update the withSerwist configuration to
set reloadOnOnline to false, preventing reconnect-triggered reloads while
preserving the other service-worker options.
| "@radix-ui/react-toggle": "^1.1.6", | ||
| "@radix-ui/react-toggle-group": "^1.1.7", | ||
| "@radix-ui/react-tooltip": "^1.2.4", | ||
| "@serwist/next": "^9.5.11", |
There was a problem hiding this comment.
π― Functional Correctness | π‘ Minor | β‘ Quick win
π§© Analysis chain
π Script executed:
#!/bin/bash
rg -n '"serwist"\s*:' package.jsonRepository: Deen-Bridge/dnb-frontend
Length of output: 162
π Script executed:
#!/bin/bash
set -euo pipefail
echo '--- package.json (dependency section) ---'
nl -ba package.json | sed -n '1,140p'
echo
echo '--- app/sw.js import lines ---'
nl -ba app/sw.js | sed -n '1,120p'Repository: Deen-Bridge/dnb-frontend
Length of output: 244
π Script executed:
#!/bin/bash
set -euo pipefail
echo '--- package.json (dependencies/devDependencies) ---'
python3 - <<'PY'
from pathlib import Path
import json
p = Path("package.json")
data = json.loads(p.read_text())
for section in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
if section in data:
print(f"\n[{section}]")
for k, v in data[section].items():
print(f"{k}: {v}")
PY
echo
echo '--- app/sw.js (imports) ---'
python3 - <<'PY'
from pathlib import Path
p = Path("app/sw.js")
for i, line in enumerate(p.read_text().splitlines(), 1):
if "serwist" in line or "import" in line:
print(f"{i}: {line}")
PYRepository: Deen-Bridge/dnb-frontend
Length of output: 2433
π Script executed:
#!/bin/bash
set -euo pipefail
echo '--- repo files mentioning serwist ---'
python3 - <<'PY'
from pathlib import Path
for path in Path('.').rglob('*'):
if path.is_file() and path.suffix in {'.json', '.yaml', '.yml', '.js', '.ts', '.mjs', '.cjs'}:
try:
text = path.read_text()
except Exception:
continue
if 'serwist' in text:
print(path)
PY
echo
echo '--- lockfile/package manager hints ---'
python3 - <<'PY'
from pathlib import Path
for name in ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"]:
p = Path(name)
print(f"{name}: {'yes' if p.exists() else 'no'}")
PYRepository: Deen-Bridge/dnb-frontend
Length of output: 5479
Add serwist as a direct devDependency
app/sw.js imports from serwist, but package.json only adds @serwist/next. The build is currently depending on a transitive install path, so declare serwist directly to keep the service-worker bundle stable across clean installs.
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` at line 41, Add serwist as a direct devDependency in
package.json alongside `@serwist/next`, so the app/sw.js import resolves from an
explicitly declared package during clean installs.
Summary by CodeRabbit