Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c3e72cb
footers are fetched dynamically
CBibop12 Sep 1, 2025
8045fbd
Merge remote-tracking branch 'origin/main' into development
remko48 Sep 1, 2025
0174cbb
better filtering function
CBibop12 Sep 1, 2025
22e5f10
dynamic top menu and menu utils put out of the brackets
CBibop12 Sep 4, 2025
44bca68
dynamic pages
CBibop12 Sep 4, 2025
06b139a
safe usage of window.session
CBibop12 Sep 4, 2025
fc8d8fe
new footer object
CBibop12 Sep 12, 2025
1b95e69
we don't use processMenuTemplate anymore
CBibop12 Sep 12, 2025
7d496e2
new icon rendering
CBibop12 Sep 12, 2025
9ce4454
Small fixes
remko48 Sep 12, 2025
9949a9b
Merge pull request #39 from ConductionNL/feature/WOO-411/dynamic-footer
CBibop12 Sep 15, 2025
aecaf82
Merge remote-tracking branch 'origin/development' into feature/WOO-41…
remko48 Sep 15, 2025
591a416
Merge remote-tracking branch 'origin/development' into feature/WOO-41…
remko48 Sep 15, 2025
9722c8c
unnecessary secondary menu removed
CBibop12 Sep 15, 2025
9338ad7
/page/{pageName} => /{pageName}
CBibop12 Sep 15, 2025
a24674b
footer optimised
CBibop12 Sep 15, 2025
c0a51c7
Merge pull request #40 from ConductionNL/feature/WOO-412/dynamic-nav
CBibop12 Sep 15, 2025
742c768
Merge remote-tracking branch 'origin/development' into feature/WOO-41…
remko48 Sep 15, 2025
ae046ba
better fetched pages
CBibop12 Sep 16, 2025
ff5aa0b
Cleanup
remko48 Sep 16, 2025
27caec1
Build fix
remko48 Sep 16, 2025
a8532da
Merge pull request #41 from ConductionNL/feature/WOO-413/dynamic-pages
CBibop12 Sep 16, 2025
179e5cc
Updated footer to use more values and updated header
remko48 Sep 17, 2025
2d919b0
Merge pull request #43 from ConductionNL/feature/WOO-413/dynamic-pages
remko48 Sep 17, 2025
8ba24c5
Logo is not minimised anymore
CBibop12 Oct 9, 2025
57115dd
Merge pull request #45 from ConductionNL/feature/WOO-424/svg-minimize…
CBibop12 Oct 9, 2025
8a08747
error screen
CBibop12 Oct 20, 2025
8fd5caa
possible docker fix
CBibop12 Oct 20, 2025
1e00615
Merge pull request #50 from ConductionNL/feature/WOO-431/error-handling
remko48 Oct 21, 2025
f6a8c61
Merge remote-tracking branch 'origin/main' into development
remko48 Oct 22, 2025
6338f3c
updated docker file
remko48 Oct 22, 2025
8dfcf4b
updated package.lock.json
remko48 Oct 22, 2025
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
939 changes: 927 additions & 12 deletions pwa/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@types/qs": "^6.9.14",
"@types/react": "^18.2.74",
"@types/react-dom": "^18.2.23",
"@utrecht/component-library-react": "^3.0.1-alpha.27",
"@utrecht/component-library-react": "9.0.3",
"@utrecht/design-tokens": "^1.0.0-alpha.640",
"axios": "^1.7.9",
"clsx": "^2.1.0",
Expand Down
10 changes: 10 additions & 0 deletions pwa/src/apiService/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import OpenWoo from "./resources/openWoo";
import FooterContent from "./resources/footerContent";
import Markdown from "./resources/markdown";
import AvailableFilters from "./resources/availableFilters";
import MenusApi from "./resources/menus";
import PagesApi from "./resources/pages";

interface PromiseMessage {
loading?: string;
Expand Down Expand Up @@ -74,6 +76,14 @@ export default class APIService {
return new Markdown(this.MarkdownClient, this.Send);
}

public get Menus(): MenusApi {
return new MenusApi(this.BaseClient, this.Send);
}

public get Pages(): PagesApi {
return new PagesApi(this.BaseClient, this.Send);
}

// Send method
public Send: TSendFunction = (instance, method, endpoint, payload, promiseMessage) => {
const _payload = JSON.stringify(payload);
Expand Down
20 changes: 20 additions & 0 deletions pwa/src/apiService/resources/menus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AxiosInstance } from "axios";
import { TSendFunction } from "../apiService";

export default class MenusApi {
private _instance: AxiosInstance;
private _send: TSendFunction;

constructor(_instance: AxiosInstance, send: TSendFunction) {
this._instance = _instance;
this._send = send;
}

public getAll = async (): Promise<any> => {
const { data } = await this._send(this._instance, "GET", "/menus?_limit=50");

return data;
};
}


20 changes: 20 additions & 0 deletions pwa/src/apiService/resources/pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AxiosInstance } from "axios";
import { TSendFunction } from "../apiService";

export default class PagesApi {
private _instance: AxiosInstance;
private _send: TSendFunction;

constructor(_instance: AxiosInstance, send: TSendFunction) {
this._instance = _instance;
this._send = send;
}

public getAll = async (): Promise<any> => {
const { data } = await this._send(this._instance, "GET", "/pages?_limit=50");

return data;
};
}


52 changes: 52 additions & 0 deletions pwa/src/components/errorBoundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from "react";
import { ErrorScreen } from "../errorScreen/ErrorScreen";

interface ErrorBoundaryProps {
children: React.ReactNode;
}

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: React.ErrorInfo | null;
}

export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}

static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return {
hasError: true,
error,
};
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
console.error("ErrorBoundary caught an error:", error, errorInfo);
this.setState({
error,
errorInfo,
});
}

render() {
if (this.state.hasError) {
return (
<ErrorScreen
error={this.state.error}
errorInfo={this.state.errorInfo}
/>
);
}

return this.props.children;
}
}

115 changes: 115 additions & 0 deletions pwa/src/components/errorScreen/ErrorScreen.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
.wrapper {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--utrecht-document-background-color, #f5f5f5);
}

.container {
max-width: 800px;
margin: 0 auto;
padding: var(--spacing-xl, 2rem);
text-align: center;
}

.iconContainer {
margin-bottom: var(--spacing-lg, 1.5rem);
}

.icon {
font-size: 4rem;
color: var(--utrecht-alert-error-color, #d32f2f);
}

.title {
margin-bottom: var(--spacing-md, 1rem);
color: var(--utrecht-heading-1-color, #1a1a1a);
font-size: 2rem;
}

.description {
margin-bottom: var(--spacing-lg, 1.5rem);
font-size: 1.1rem;
color: var(--utrecht-paragraph-color, #666);
line-height: 1.6;
}

.alert {
margin: var(--spacing-lg, 1.5rem) 0;
padding: var(--spacing-md, 1rem);
text-align: left;
border-radius: var(--border-radius-md, 4px);
background-color: #ffebee;
border-left: 4px solid #d32f2f;
}

.errorTitle {
margin-bottom: var(--spacing-sm, 0.5rem);
font-size: 1.2rem;
color: #c62828;
}

.errorMessage {
margin: 0;
font-size: 0.95rem;
color: #5f2120;
word-break: break-word;
}

.detailsSection {
margin: var(--spacing-lg, 1.5rem) 0;
}

.toggleButton {
margin-bottom: var(--spacing-md, 1rem);
}

.technicalDetails {
text-align: left;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: var(--border-radius-md, 4px);
padding: var(--spacing-md, 1rem);
margin-top: var(--spacing-md, 1rem);
}

.stackTitle {
font-size: 1rem;
margin: var(--spacing-md, 1rem) 0 var(--spacing-sm, 0.5rem);
color: #333;
}

.stackTrace {
background: #fff;
border: 1px solid #ddd;
border-radius: var(--border-radius-sm, 2px);
padding: var(--spacing-md, 1rem);
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
font-size: 0.85rem;
color: #333;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
}

/* Responsive design */
@media (max-width: 768px) {
.container {
padding: var(--spacing-md, 1rem);
}

.title {
font-size: 1.5rem;
}

.description {
font-size: 1rem;
}

.icon {
font-size: 3rem;
}
}

78 changes: 78 additions & 0 deletions pwa/src/components/errorScreen/ErrorScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from "react";
import * as styles from "./ErrorScreen.module.css";
import {
Page,
PageContent,
Heading1,
Heading2,
Paragraph,
Button,
Alert,
} from "@utrecht/component-library-react/dist/css-module";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { useTranslation } from "react-i18next";

interface ErrorScreenProps {
error: Error | null;
errorInfo?: React.ErrorInfo | null;
}

export const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, errorInfo }) => {
const { t } = useTranslation();
const [showDetails, setShowDetails] = React.useState(false);

return (
<div className={styles.wrapper}>
<Page>
<PageContent className={styles.container}>
<div className={styles.iconContainer}>
<FontAwesomeIcon icon={faExclamationTriangle} className={styles.icon} />
</div>

<Heading1 className={styles.title}>Oops, {t("something went wrong")}!</Heading1>

<Paragraph className={styles.description}>
{t(
"We encountered an error while loading the data from the municipality. This might be due to incorrect or incomplete data.",
)}
</Paragraph>

{error && (
<Alert className={styles.alert} type="error">
<Heading2 className={styles.errorTitle}>{t("Error details")}:</Heading2>
<Paragraph className={styles.errorMessage}>
<strong>{error.name}:</strong> {error.message}
</Paragraph>
</Alert>
)}

{errorInfo && (
<div className={styles.detailsSection}>
<Button
appearance="secondary-action-button"
className={styles.toggleButton}
onClick={() => setShowDetails(!showDetails)}
>
{showDetails ? t("Hide technical details") : t("Show technical details")}
</Button>

{showDetails && (
<div className={styles.technicalDetails}>
<pre className={styles.stackTrace}>{errorInfo.componentStack}</pre>
{error?.stack && (
<>
<Heading2 className={styles.stackTitle}>Stack Trace:</Heading2>
<pre className={styles.stackTrace}>{error.stack}</pre>
</>
)}
</div>
)}
</div>
)}
</PageContent>
</Page>
</div>
);
};

27 changes: 27 additions & 0 deletions pwa/src/hooks/menus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";
import { useQuery } from "react-query";
import APIService from "../apiService/apiService";
import APIContext from "../apiService/apiContext";

export const useMenus = () => {
const API: APIService | null = React.useContext(APIContext);

const getAll = () =>
useQuery<any, Error>(
[
"Menus",
typeof window !== "undefined" ? window.sessionStorage.getItem("OIDN_NUMBER") : undefined,
],
() => API?.Menus.getAll(),
{
onError: (error) => {
console.warn(error.message);
},
enabled: typeof window !== "undefined",
},
);

return { getAll };
};


27 changes: 27 additions & 0 deletions pwa/src/hooks/pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";
import { useQuery } from "react-query";
import APIService from "../apiService/apiService";
import APIContext from "../apiService/apiContext";

export const usePages = () => {
const API: APIService | null = React.useContext(APIContext);

const getAll = () =>
useQuery<any, Error>(
[
"Pages",
typeof window !== "undefined" ? window.sessionStorage.getItem("OIDN_NUMBER") : undefined,
],
() => API?.Pages.getAll(),
{
onError: (error) => {
console.warn(error.message);
},
enabled: typeof window !== "undefined",
},
);

return { getAll };
};


Loading
Loading