-
Notifications
You must be signed in to change notification settings - Fork 0
setup error handling including demo #88
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: dev
Are you sure you want to change the base?
Changes from 2 commits
e94e0e7
7d5f99a
94b4776
c294012
8341a37
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,24 @@ | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
|
|
||
| const filePath = path.join(process.cwd(), 'src', 'client', 'index.ts'); | ||
|
|
||
| try { | ||
| if (fs.existsSync(filePath)) { | ||
| const content = fs.readFileSync(filePath, 'utf8'); | ||
| if (!content.includes("'use client'")) { | ||
| const newContent = `'use client';\n\n${content}`; | ||
| fs.writeFileSync(filePath, newContent, 'utf8'); | ||
| console.log(`Added 'use client' to ${filePath}`); | ||
| } else { | ||
| console.log(`'use client' already present in ${filePath}`); | ||
| } | ||
| } else { | ||
| console.error(`File not found: ${filePath}`); | ||
| process.exit(1); | ||
| } | ||
| } catch (err) { | ||
| console.error('Error adding use client directive:', err); | ||
| process.exit(1); | ||
| } | ||
|
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -33,7 +33,7 @@ | |||||
| }, | ||||||
| "scripts": { | ||||||
| "build:remove": "rimraf dist && rimraf src/client/index.ts && rimraf src/client/styles.css && rimraf src/server/index.ts && rimraf src/globals/index.ts", | ||||||
| "build:barrels": "npm run build:remove && barrelsby -c barrels-client.scripts.json && barrelsby -c barrels-server.scripts.json && barrelsby -c barrels-globals.scripts.json", | ||||||
| "build:barrels": "npm run build:remove && barrelsby -c barrels-client.scripts.json && barrelsby -c barrels-server.scripts.json && barrelsby -c barrels-globals.scripts.json && node add-use-client.mjs", | ||||||
| "build:styles": "node barrels.styles.mjs && node update-barrels.mjs", | ||||||
| "build:types": "tsc --project tsconfig.build.json", | ||||||
| "build:prebuild": "npm run build:barrels && npm run build:styles && npm run build:types", | ||||||
|
|
@@ -87,7 +87,7 @@ | |||||
| "@deck.gl/layers": "^9.2.2", | ||||||
| "@deck.gl/react": "^9.2.2", | ||||||
| "@gisatcz/deckgl-geolib": "1.12.0-dev.5", | ||||||
| "@gisatcz/ptr-be-core": "^0.0.1-dev.9", | ||||||
| "@gisatcz/ptr-be-core": "^0.0.2", | ||||||
|
||||||
| "@gisatcz/ptr-be-core": "^0.0.2", | |
| "@gisatcz/ptr-be-core": "^0.0.7", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,8 @@ export default [ | |
| { | ||
| input: 'src/client/index.ts', // Entry point of the library | ||
| output: [ | ||
| {file: pkg.main, format: 'cjs', sourcemap: true}, // CommonJS output | ||
| {file: pkg.module, format: 'esm', sourcemap: true} // ES module output | ||
| {file: pkg.main, format: 'cjs', sourcemap: true, banner: "'use client';"}, // CommonJS output | ||
|
||
| {file: pkg.module, format: 'esm', sourcemap: true, banner: "'use client';"} // ES module output | ||
| ], | ||
| external: id => { | ||
| const externals = [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| 'use client'; | ||
|
||
|
|
||
| import { createContext, useState, useCallback, useContext, ReactNode, useEffect } from 'react'; | ||
|
|
||
| /** | ||
| * Shape of a single error item. | ||
| */ | ||
| export interface AppError { | ||
| id: string; | ||
| message: string; | ||
| title?: string; | ||
| type?: 'error' | 'warning' | 'info'; | ||
| autoClose?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * The shape of the error context. | ||
| */ | ||
| interface ErrorContextType { | ||
| errors: AppError[]; | ||
| addError: (error: Omit<AppError, 'id'>) => void; | ||
| removeError: (id: string) => void; | ||
| } | ||
|
|
||
| /** | ||
| * React Context for global error state management. | ||
| */ | ||
| const ErrorContext = createContext<ErrorContextType | undefined>(undefined); | ||
|
|
||
| /** | ||
| * Custom hook to access the ErrorContext. | ||
| * Throws an error if used outside of an ErrorProvider. | ||
| */ | ||
| export const useError = () => { | ||
| const context = useContext(ErrorContext); | ||
| if (!context) { | ||
| throw new Error('useError must be used within an ErrorProvider'); | ||
| } | ||
| return context; | ||
| }; | ||
|
|
||
| /** | ||
| * Provider component that encapsulates the error state and logic. | ||
| */ | ||
| export const ErrorProvider = ({ children }: { children: ReactNode }) => { | ||
| const [errors, setErrors] = useState<AppError[]>([]); | ||
|
|
||
| const removeError = useCallback((id: string) => { | ||
| setErrors((currentErrors) => currentErrors.filter((err) => err.id !== id)); | ||
| }, []); | ||
|
|
||
| const addError = useCallback((error: Omit<AppError, 'id'>) => { | ||
| const id = Math.random().toString(36).substring(7); | ||
| const newError = { ...error, id }; | ||
| setErrors((currentErrors) => [...currentErrors, newError]); | ||
|
|
||
| if (error.autoClose !== false) { | ||
| setTimeout(() => { | ||
| removeError(id); | ||
| }, 5000); | ||
| } | ||
| }, [removeError]); | ||
|
|
||
| // Effect to listen for custom events to add errors from outside React components | ||
| useEffect(() => { | ||
| const handleAddErrorEvent = (event: Event) => { | ||
| const errorDetail = (event as CustomEvent).detail; | ||
| if (errorDetail) { | ||
| addError(errorDetail); | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener('add-app-error', handleAddErrorEvent); | ||
| return () => { | ||
| window.removeEventListener('add-app-error', handleAddErrorEvent); | ||
| }; | ||
| }, [addError]); | ||
|
|
||
|
|
||
| return ( | ||
| <ErrorContext.Provider value={{ errors, addError, removeError }}> | ||
| {children} | ||
| </ErrorContext.Provider> | ||
| ); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| 'use client'; | ||
|
|
||
| import { useEffect } from 'react'; | ||
| import { useError } from './ErrorContext'; | ||
| import { HandledError } from './store'; | ||
| import './errorStyle.css'; | ||
|
|
||
| /** | ||
| * Global error notification renderer. | ||
| * | ||
| * This component: | ||
| * - consumes the `ErrorContext` to display all current errors as dismissible notifications. | ||
| * - subscribes to global `window` events (`error` and `unhandledrejection`) | ||
| * to automatically capture and display unexpected runtime errors. | ||
| * | ||
| * It ignores `HandledError` instances to prevent duplicate notifications when | ||
| * the `throwError` utility is used. | ||
| */ | ||
| export const ErrorNotifications = () => { | ||
| const { errors, removeError, addError } = useError(); | ||
|
|
||
| useEffect(() => { | ||
| const handleGlobalError = (event: ErrorEvent) => { | ||
| // Ignore errors that have already been handled by our system. | ||
| if (event.error instanceof HandledError) return; | ||
|
|
||
| addError({ | ||
| title: 'Unexpected Error', | ||
| message: event.message || 'An unexpected error occurred', | ||
| type: 'error', | ||
| }); | ||
| }; | ||
|
|
||
| const handleUnhandledRejection = (event: PromiseRejectionEvent) => { | ||
| // Ignore promise rejections from errors that have already been handled. | ||
| if (event.reason instanceof HandledError) return; | ||
|
|
||
| addError({ | ||
| title: 'Unhandled Promise Rejection', | ||
| message: event.reason?.message || String(event.reason), | ||
| type: 'error', | ||
| }); | ||
| }; | ||
|
|
||
| window.addEventListener('error', handleGlobalError); | ||
| window.addEventListener('unhandledrejection', handleUnhandledRejection); | ||
|
|
||
| // Cleanup listeners on component unmount. | ||
| return () => { | ||
| window.removeEventListener('error', handleGlobalError); | ||
| window.removeEventListener('unhandledrejection', handleUnhandledRejection); | ||
| }; | ||
| }, [addError]); | ||
|
|
||
| if (errors.length === 0) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div className='errorNotification-position-div'> | ||
| {errors.map((error) => ( | ||
| <div | ||
| key={error.id} | ||
| className={ | ||
| error.type === 'warning' | ||
| ? 'errorNotification-div errorNotification-div-warningType' | ||
| : 'errorNotification-div errorNotification-div-errorType' | ||
| } | ||
| > | ||
| <div> | ||
| {error.title && <strong>{error.title}</strong>} | ||
| <div>{error.message}</div> | ||
| </div> | ||
| <button | ||
| onClick={() => removeError(error.id)} | ||
| className="errorNotification-closeBtn" | ||
| > | ||
| × | ||
| </button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import './errorStyle.css'; | ||
|
|
||
| /** | ||
| * Props for the global error component used by consuming applications. | ||
| * | ||
| * - `error` – The error object caught by a Next.js error boundary. In addition | ||
| * to the standard `Error` fields, it may contain a `digest` field | ||
| * that Next.js uses internally to identify/log the error. | ||
| * - `reset` – Callback provided by Next.js that allows the UI to request | ||
| * a retry. When called, Next.js will try to re-render the | ||
| * affected route segment and recover from the error. | ||
| */ | ||
| interface GlobalErrorProps { | ||
| error: Error & { digest?: string }; | ||
| reset: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * Reusable global error UI for applications using the ptr-fe-core package. | ||
| * | ||
| * This component is intended to be used from the app's root `error.tsx` | ||
| * boundary. It displays a generic error message, shows the error's message | ||
| * text, and provides a “Try again” button which invokes the `reset` callback | ||
| * so Next.js can attempt to re-render and recover from the failure. | ||
| */ | ||
| export const GlobalError = ({ error, reset }: GlobalErrorProps) => { | ||
| return ( | ||
| <div className="globalError-div"> | ||
| <h2>Something went wrong!</h2> | ||
| <p>{error.message}</p> | ||
| <button | ||
| onClick={() => reset()} | ||
| className="globalError-btn"> | ||
| Try again | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
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.
this is pure Next syntax and should not to be used in general FE NPM.