-
Notifications
You must be signed in to change notification settings - Fork 73
Add App Router error boundaries, Sentry monitoring, and PII scrubbing… #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
04ba008
c45fccd
eb410d2
14ccd29
37c25df
81386ae
ec2af96
fb23912
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| legacy-peer-deps=true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import * as Sentry from "@sentry/nextjs"; | ||
|
|
||
| export default function CoursesError({ error, reset }) { | ||
| const [eventId, setEventId] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| const id = Sentry.captureException(error); | ||
| setEventId(id); | ||
| }, [error]); | ||
|
|
||
| return ( | ||
| <div className="flex-1 w-full flex flex-col items-center justify-center px-6 py-16 text-center"> | ||
| <div className="max-w-md"> | ||
| <h2 className="text-xl font-bold text-[#252F40] mb-3"> | ||
| Couldn't load this course | ||
| </h2> | ||
| <p className="text-sm text-gray-500 mb-8"> | ||
| We ran into a problem fetching your course content. Please retry. | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={() => reset()} | ||
| className="px-5 py-2.5 rounded-lg bg-[#34AD5D] text-white text-sm font-medium hover:bg-[#2c9350] transition-colors" | ||
| > | ||
| Retry | ||
| </button> | ||
| {eventId && ( | ||
| <p className="mt-6 text-xs text-gray-400">Report ID: {eventId}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import * as Sentry from "@sentry/nextjs"; | ||
|
|
||
| // Renders inside app/dashboard/layout.jsx's <SidebarInset>, so the sidebar | ||
| // and nav header stay usable while just this segment shows the fallback. | ||
| export default function DashboardError({ error, reset }) { | ||
| const [eventId, setEventId] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| const id = Sentry.captureException(error); | ||
| setEventId(id); | ||
| }, [error]); | ||
|
|
||
| return ( | ||
| <div className="flex-1 w-full flex flex-col items-center justify-center px-6 py-16 text-center"> | ||
| <div className="max-w-md"> | ||
| <h2 className="text-xl font-bold text-[#252F40] mb-3"> | ||
| This page ran into a problem | ||
| </h2> | ||
| <p className="text-sm text-gray-500 mb-8"> | ||
| Something went wrong loading your dashboard. Try again, or use the | ||
| sidebar to head somewhere else. | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={() => reset()} | ||
| className="px-5 py-2.5 rounded-lg bg-[#34AD5D] text-white text-sm font-medium hover:bg-[#2c9350] transition-colors" | ||
| > | ||
| Try again | ||
| </button> | ||
| {eventId && ( | ||
| <p className="mt-6 text-xs text-gray-400">Report ID: {eventId}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import * as Sentry from "@sentry/nextjs"; | ||
|
|
||
| export default function LibraryError({ error, reset }) { | ||
| const [eventId, setEventId] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| const id = Sentry.captureException(error); | ||
| setEventId(id); | ||
| }, [error]); | ||
|
|
||
| return ( | ||
| <div className="flex-1 w-full flex flex-col items-center justify-center px-6 py-16 text-center"> | ||
| <div className="max-w-md"> | ||
| <h2 className="text-xl font-bold text-[#252F40] mb-3"> | ||
| Couldn't load your library | ||
| </h2> | ||
| <p className="text-sm text-gray-500 mb-8"> | ||
| We ran into a problem fetching this content. Please retry. | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={() => reset()} | ||
| className="px-5 py-2.5 rounded-lg bg-[#34AD5D] text-white text-sm font-medium hover:bg-[#2c9350] transition-colors" | ||
| > | ||
| Retry | ||
| </button> | ||
| {eventId && ( | ||
| <p className="mt-6 text-xs text-gray-400">Report ID: {eventId}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import * as Sentry from "@sentry/nextjs"; | ||
|
|
||
| // Kept dependency-light on purpose (no toasts, providers, or design-system | ||
| // imports) so this boundary can't itself throw while trying to render a | ||
| // recovery UI for an unrelated error. | ||
| export default function Error({ error, reset }) { | ||
| const [eventId, setEventId] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| const id = Sentry.captureException(error); | ||
| setEventId(id); | ||
| }, [error]); | ||
|
|
||
| return ( | ||
| <div className="min-h-[60vh] w-full flex flex-col items-center justify-center px-6 py-16 text-center"> | ||
| <div className="max-w-md"> | ||
| <h1 className="text-2xl font-bold text-[#252F40] mb-3"> | ||
| Something went wrong | ||
| </h1> | ||
| <p className="text-sm text-gray-500 mb-8"> | ||
| An unexpected error occurred while loading this page. You can try | ||
| again, or head back to the homepage. | ||
| </p> | ||
| <div className="flex items-center justify-center gap-3"> | ||
| <button | ||
| type="button" | ||
| onClick={() => reset()} | ||
| className="px-5 py-2.5 rounded-lg bg-[#34AD5D] text-white text-sm font-medium hover:bg-[#2c9350] transition-colors" | ||
| > | ||
| Try again | ||
| </button> | ||
| <a | ||
| href="/" | ||
| className="px-5 py-2.5 rounded-lg border border-gray-200 text-[#252F40] text-sm font-medium hover:bg-gray-50 transition-colors" | ||
| > | ||
| Go home | ||
| </a> | ||
| </div> | ||
| {eventId && ( | ||
| <p className="mt-6 text-xs text-gray-400">Report ID: {eventId}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import * as Sentry from "@sentry/nextjs"; | ||
|
|
||
| // This only activates in production builds (test with `npm run build && npm | ||
| // start`). No app providers, fonts, or global stylesheet are guaranteed to | ||
| // be available here, so this stays fully self-contained with inline styles | ||
| // and renders its own <html>/<body>. | ||
| export default function GlobalError({ error, reset }) { | ||
| const [eventId, setEventId] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| const id = Sentry.captureException(error); | ||
| setEventId(id); | ||
| }, [error]); | ||
|
|
||
| return ( | ||
| <html lang="en"> | ||
| <body | ||
| style={{ | ||
| margin: 0, | ||
| minHeight: "100vh", | ||
| display: "flex", | ||
| flexDirection: "column", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| padding: "24px", | ||
| textAlign: "center", | ||
| fontFamily: | ||
| "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif", | ||
| backgroundColor: "#ffffff", | ||
| color: "#252F40", | ||
| }} | ||
| > | ||
| <div style={{ maxWidth: 420 }}> | ||
| <h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 12 }}> | ||
| Deen Bridge | ||
| </h1> | ||
| <p style={{ fontSize: 15, color: "#6b7280", marginBottom: 28 }}> | ||
| Something went wrong and this page couldn't load. Please | ||
| reload to try again. | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={() => reset()} | ||
| style={{ | ||
| padding: "10px 20px", | ||
| borderRadius: 8, | ||
| border: "none", | ||
| backgroundColor: "#34AD5D", | ||
| color: "#ffffff", | ||
| fontSize: 14, | ||
| fontWeight: 500, | ||
| cursor: "pointer", | ||
| }} | ||
| > | ||
| Reload | ||
| </button> | ||
| {eventId && ( | ||
| <p style={{ marginTop: 20, fontSize: 12, color: "#9ca3af" }}> | ||
| Report ID: {eventId} | ||
| </p> | ||
| )} | ||
| </div> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,22 @@ export const metadata = { | |||||||||
| 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", | ||||||||||
| icons: { | ||||||||||
|
Comment on lines
+21
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
git ls-files 'app/layout.js' 'app/**/manifest.*' 'public/sw.js'
printf '\n== outline: app/layout.js ==\n'
ast-grep outline app/layout.js --view expanded || true
printf '\n== relevant layout lines ==\n'
sed -n '1,120p' app/layout.js
printf '\n== manifest files ==\n'
for f in $(git ls-files 'app/**/manifest.*'); do
echo "--- $f ---"
wc -l "$f"
sed -n '1,220p' "$f"
done
printf '\n== sw.js manifest references ==\n'
rg -n 'manifest|webmanifest' public/sw.js app -g '!**/node_modules/**' || trueRepository: Deen-Bridge/dnb-frontend Length of output: 33106 Fix the manifest URL 🐛 Proposed fix- manifest: "/manifest",
+ manifest: "/manifest.webmanifest",📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| icon: [ | ||||||||||
| { url: "/favicon.ico", sizes: "any" }, | ||||||||||
| { url: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png" }, | ||||||||||
| { url: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png" }, | ||||||||||
| ], | ||||||||||
| apple: [ | ||||||||||
| { url: "/icons/icon-192x192.png", sizes: "192x192" }, | ||||||||||
| ], | ||||||||||
| }, | ||||||||||
| appleWebApp: { | ||||||||||
| capable: true, | ||||||||||
| title: "DeenBridge", | ||||||||||
| statusBarStyle: "black-translucent", | ||||||||||
| }, | ||||||||||
| openGraph: { | ||||||||||
| title: "Deen Bridge ", | ||||||||||
| description: | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Install banner keeps showing but silently stops working after a dismissed prompt.
installableis only reset whenoutcome === "accepted". If the user dismisses the native prompt,deferredPromptis nulled butinstallablestaystrue, so a subsequent click on "Install" silently no-ops (the!deferredPromptguard returns early). The banner should also hide (or re-arm) after a dismissal.♻️ 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
🤖 Prompt for AI Agents