- Changed API and Keycloak URLs in `.env` and `.env.development`. - Updated `vite.config.js` to reflect new allowed hosts. - Refactored components to use `useContent` context instead of individual contexts for blogs, companies, and projects. - Improved error handling in `App` component with `AppError`. - Added FPS monitoring to `ParticlesBackground` component for developer mode. - Removed unused contexts and adjusted related imports. - Enhanced styling for various components and added new styles for FPS monitor. - Cleaned up SVG files by removing unnecessary attributes.
161 lines
3.5 KiB
JavaScript
161 lines
3.5 KiB
JavaScript
import PropTypes from "prop-types";
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
|
|
const DEFAULT_AUTO_HIDE_MS = 5000;
|
|
|
|
const MessageContext = createContext({
|
|
isVisible: false,
|
|
message: null,
|
|
icon: null,
|
|
showMessage: () => {},
|
|
hideMessage: () => {},
|
|
});
|
|
|
|
export const MessageProvider = ({
|
|
children,
|
|
defaultAutoHideDelay = DEFAULT_AUTO_HIDE_MS,
|
|
}) => {
|
|
const [state, setState] = useState({
|
|
message: null,
|
|
icon: null,
|
|
isVisible: false,
|
|
});
|
|
|
|
const hideTimeoutRef = useRef(null);
|
|
const clearHideTimeout = useCallback(() => {
|
|
if (hideTimeoutRef.current) {
|
|
window.clearTimeout(hideTimeoutRef.current);
|
|
hideTimeoutRef.current = null;
|
|
}
|
|
}, []);
|
|
|
|
const hideMessage = useCallback(() => {
|
|
clearHideTimeout();
|
|
setState((prev) => ({
|
|
...prev,
|
|
isVisible: false,
|
|
}));
|
|
}, [clearHideTimeout]);
|
|
|
|
const showMessage = useCallback(
|
|
(messageText, icon = null, autoHide = true) => {
|
|
if (!messageText) {
|
|
console.warn("showMessage called without message text");
|
|
return;
|
|
}
|
|
|
|
clearHideTimeout();
|
|
|
|
setState({
|
|
message: messageText,
|
|
icon,
|
|
isVisible: true,
|
|
});
|
|
|
|
const resolvedDelay =
|
|
typeof autoHide === "number" && autoHide > 0
|
|
? autoHide
|
|
: defaultAutoHideDelay;
|
|
|
|
if (autoHide && typeof window !== "undefined") {
|
|
hideTimeoutRef.current = window.setTimeout(() => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
isVisible: false,
|
|
}));
|
|
hideTimeoutRef.current = null;
|
|
}, resolvedDelay);
|
|
}
|
|
},
|
|
[clearHideTimeout, defaultAutoHideDelay]
|
|
);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
clearHideTimeout();
|
|
};
|
|
}, [clearHideTimeout]);
|
|
|
|
const contextValue = useMemo(
|
|
() => ({
|
|
isVisible: state.isVisible,
|
|
message: state.message,
|
|
icon: state.icon,
|
|
showMessage,
|
|
hideMessage,
|
|
}),
|
|
[state.isVisible, state.message, state.icon, showMessage, hideMessage]
|
|
);
|
|
|
|
return (
|
|
<MessageContext.Provider value={contextValue}>
|
|
{children}
|
|
<MessageOverlay
|
|
message={state.message}
|
|
icon={state.icon}
|
|
isVisible={state.isVisible}
|
|
onDismiss={hideMessage}
|
|
/>
|
|
</MessageContext.Provider>
|
|
);
|
|
};
|
|
|
|
MessageProvider.propTypes = {
|
|
children: PropTypes.node.isRequired,
|
|
defaultAutoHideDelay: PropTypes.number,
|
|
};
|
|
|
|
export const useMessage = () => useContext(MessageContext);
|
|
|
|
const MessageOverlay = ({ message, icon, isVisible, onDismiss }) => {
|
|
const shouldRender = Boolean(message);
|
|
|
|
if (!shouldRender) {
|
|
return null;
|
|
}
|
|
|
|
const className = [
|
|
"tb-message-popup",
|
|
isVisible ? "tb-message-popup-visible" : "tb-message-popup-hidden",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
return (
|
|
<div
|
|
className={className}
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-hidden={!isVisible}
|
|
>
|
|
<div className="tb-message-popup-content">
|
|
{icon ? <span className="tb-message-popup-icon">{icon}</span> : null}
|
|
<span className="tb-message-popup-text">{message}</span>
|
|
<button
|
|
type="button"
|
|
className="tb-message-popup-dismiss"
|
|
onClick={onDismiss}
|
|
aria-label="Dismiss message"
|
|
>
|
|
{"\u00d7"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
MessageOverlay.propTypes = {
|
|
message: PropTypes.string,
|
|
icon: PropTypes.node,
|
|
isVisible: PropTypes.bool.isRequired,
|
|
onDismiss: PropTypes.func.isRequired,
|
|
};
|