Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions add-use-client.mjs
Copy link
Collaborator

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.

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);
}

34 changes: 22 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"@gisatcz/ptr-be-core": "^0.0.2",
"@gisatcz/ptr-be-core": "^0.0.7",

"@mantine/core": "^8.0.2",
"@mantine/hooks": "^8.0.2",
"@tabler/icons-react": "^3.31.0",
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

No NextJS magic in NPM please. We can handle this part per Next app, not in NPM. Also we dont want to have all mandatory parts use client by default (from layouts etc.)

{file: pkg.module, format: 'esm', sourcemap: true, banner: "'use client';"} // ES module output
],
external: id => {
const externals = [
Expand Down
86 changes: 86 additions & 0 deletions src/client/shared/errors/ErrorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use client';
Copy link
Collaborator

Choose a reason for hiding this comment

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

nope


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>
);
};

85 changes: 85 additions & 0 deletions src/client/shared/errors/ErrorNotifications.tsx
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"
>
&times;
</button>
</div>
))}
</div>
);
};

39 changes: 39 additions & 0 deletions src/client/shared/errors/GlobalError.tsx
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>
);
};

Loading