Skip to content

Commit

Permalink
Merge pull request #117 from kieler/116-error-handling-in-management
Browse files Browse the repository at this point in the history
116 error handling in management
  • Loading branch information
n1kPLV authored Sep 6, 2023
2 parents 237748f + cae8ed5 commit a801102
Show file tree
Hide file tree
Showing 38 changed files with 1,916 additions and 1,224 deletions.
695 changes: 695 additions & 0 deletions Website/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14",
"cookies-next": "^2.1.2",
"cssnano": "^6.0.1",
"jose": "^4.14.4",
"leaflet": "^1.9.4",
"leaflet-rotatedmarker": "^0.2.0",
Expand Down
1 change: 1 addition & 0 deletions Website/postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
},
}
2 changes: 1 addition & 1 deletion Website/public/poiTypeIcons/generic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 13 additions & 15 deletions Website/src/app/components/base_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './globals.css'
import "./globals.css";
import Header from "@/app/components/header";
import Footer from "@/app/components/footer";
import React from "react";

/**
* The general layout for this site to be used both for pages
Expand All @@ -16,17 +17,14 @@ import Footer from "@/app/components/footer";
* @param username The username of the currently logged-in user, or undefined if the user is not logged in.
* @constructor
*/
export default function BaseLayout({children, username}: {
children: React.ReactNode,
username?: string
}) {
return (
// inline styling using tailwind will keep styling information in the same
// file as the markup information.
<div className='h-full min-h-screen flex flex-initial flex-col'>
<Header username={username}/>
{children}
<Footer/>
</div>
)
}
export default function BaseLayout({ children, username }: { children: React.ReactNode; username?: string }) {
return (
// inline styling using tailwind will keep styling information in the same
// file as the markup information.
<div className="h-full min-h-screen flex flex-initial flex-col">
<Header username={username} />
{children}
<Footer />
</div>
);
}
12 changes: 8 additions & 4 deletions Website/src/app/components/form_map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import L, { DragEndEvent, LatLng } from "leaflet";
import "leaflet-rotatedmarker";
import "leaflet/dist/leaflet.css";
import { FullTrack } from "@/utils/api";
import { FullTrack, Position } from "@/utils/api";
import dynamic from "next/dynamic";
import { Dispatch, useEffect, useMemo, useRef } from "react";
import assert from "assert";
Expand All @@ -18,8 +18,8 @@ function InternalPositionSelector({
zoom_level,
height
}: {
position: LatLng;
setPosition: Dispatch<LatLng>;
position: Position;
setPosition: Dispatch<Position>;
setModified?: Dispatch<boolean>;
zoom_level: number;
track_data?: FullTrack;
Expand Down Expand Up @@ -49,7 +49,11 @@ function InternalPositionSelector({

markerRef.current = L.marker([0, 0], { draggable: true, icon: markerIcon }).addTo(mapRef.current);
markerRef.current?.on("dragend", (e: DragEndEvent) => {
setPosition(e.target.getLatLng());
const newPos: LatLng = e.target.getLatLng();
setPosition({
lat: newPos.lat,
lng: newPos.lng
});
if (setModified) {
setModified(true);
}
Expand Down
16 changes: 8 additions & 8 deletions Website/src/app/components/loadmap.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Spinner } from "@/app/components/spinner";

/**
* A loading page to show while the Leaflet map is loaded dynamically.
*/
export default function LoadMapScreen() {

return (
<div className='grid justify-center content-center h-full' >
<div>Loading...</div>
</div>
);

}
return (
<div className="flex gap-5 justify-center items-center content-center h-full">
<Spinner className={"h-10 w-auto"} />
<div>Loading...</div>
</div>
);
}
4 changes: 2 additions & 2 deletions Website/src/app/components/login.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { usePathname } from "next/navigation";
import { useEffect, useRef } from "react";
import { PropsWithChildren, useEffect, useRef } from "react";

import { UrlObject, format } from "url";
import Footer from "@/app/components/footer";
Expand Down Expand Up @@ -68,7 +68,7 @@ export function LoginDialog({
dst_url,
login_callback: _,
children
}: React.PropsWithChildren<{ dst_url?: Url; login_callback?: (success: boolean) => void }>) {
}: PropsWithChildren<{ dst_url?: Url; login_callback?: (success: boolean) => void }>) {
const dialogRef = useRef(null as HTMLDialogElement | null);

useEffect(() => {
Expand Down
13 changes: 13 additions & 0 deletions Website/src/app/components/reloadButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";
import { useRouter } from "next/navigation";
import { PropsWithChildren } from "react";

export function ReloadButton({ className, children }: PropsWithChildren<{ className?: string }>) {
const router = useRouter();

return (
<button className={className} onClick={() => router.refresh()}>
{children}
</button>
);
}
18 changes: 18 additions & 0 deletions Website/src/app/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function Spinner({ className }: { className?: string }) {
return (
<svg className={className} viewBox={"0 0 48 48"}>
<circle
className={"origin-center stroke-6 fill-none stroke-slate-400 dark:stroke-slate-600"}
cx={24}
cy={24}
r={20}
/>
<path
className={
"motion-safe:animate-spin-ease motion-reduce:animate-pulse origin-center stroke-6 fill-none stroke-slate-900 dark:stroke-slate-100"
}
d={"M 12 40 A 20 20 0 1 1 40 12"}
/>
</svg>
);
}
77 changes: 45 additions & 32 deletions Website/src/app/components/track_selection.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,59 @@
"use client";

import { FormEventHandler, useEffect, useRef } from "react";
import { Dispatch, FormEventHandler, PropsWithChildren, useEffect, useRef, useState } from "react";

import Footer from "@/app/components/footer";
import { RevalidateError } from "@/utils/types";
import useSWR from "swr";
import { setCookie } from "cookies-next";
import { inter } from "@/utils/common";
import { TrackList } from "@/utils/api";

const selectTrack: FormEventHandler = e => {
e.preventDefault();
const data = new FormData(e.target as HTMLFormElement);

// set the relevant cookie
setCookie("track_id", data.get("track"));

console.log(data);
// and reload
window.location.reload();
return;
};

const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
// console.log('not ok!');
throw new RevalidateError("Re-Fetching unsuccessful", res.status);
}
return (await res.json()) as TrackList;
};
import { getFetcher } from "@/utils/fetcher";
import { useRouter } from "next/navigation";
import { Spinner } from "@/app/components/spinner";

/**
* The track selection form for this web application.
*/
export default function Selection() {
export default function Selection({
completed,
setCompleted
}: {
completed: boolean;
setCompleted: Dispatch<boolean>;
}) {
// @type data TrackList
const { data, error, isLoading } = useSWR("/webapi/tracks/list", fetcher);
const { data, error, isLoading } = useSWR("/webapi/tracks/list", getFetcher<"/webapi/tracks/list">);
// get the next page router
const router = useRouter();

const selectTrack: FormEventHandler = e => {
e.preventDefault();
const data = new FormData(e.target as HTMLFormElement);

// set the relevant cookie
setCookie("track_id", data.get("track"));

// change the react state
setCompleted(true);

// and reload
router.refresh();
return;
};

return (
<form onSubmit={selectTrack} className="grid grid-cols-2 gap-y-1 my-1.5 items-center">
<form onSubmit={selectTrack} className="grid grid-cols-2 gap-y-1 my-1.5 items-center h-24">
{isLoading ? (
<p> Lädt... </p>
<div className={"flex col-span-2 justify-center items-center gap-5"}>
<Spinner className={"h-10 w-auto"} />
<div>Lädt...</div>
</div>
) : error ? (
<p> {error.toString()} </p>
<div> {error.toString()} </div>
) : completed ? (
<div className={"flex col-span-2 justify-center items-center gap-5"}>
<Spinner className={"h-10 w-auto"} />
<div>Wird gepeichert...</div>
</div>
) : (
<>
<label className={""} htmlFor="track">
Expand Down Expand Up @@ -72,9 +82,12 @@ export default function Selection() {
* The track selection form wrapped in a dialog, for easy display in a modal way.
* @param children HTML elements to display over the login form in the dialog, for example for explanations.
*/
export function SelectionDialog({ children }: React.PropsWithChildren) {
export function SelectionDialog({ children }: PropsWithChildren) {
const dialogRef = useRef(null as HTMLDialogElement | null);

// get a "completed" state
const [completed, setCompleted] = useState(false);

useEffect(() => {
if (!dialogRef.current?.open) {
dialogRef.current?.showModal();
Expand All @@ -89,7 +102,7 @@ export function SelectionDialog({ children }: React.PropsWithChildren) {
}}
className="drop-shadow-xl shadow-black bg-white p-4 rounded max-w-2xl w-full dark:bg-slate-800 dark:text-white backdrop:bg-gray-200/30 backdrop:backdrop-blur">
{children}
<Selection />
<Selection completed={completed} setCompleted={setCompleted} />
<Footer />
</dialog>
);
Expand Down
33 changes: 15 additions & 18 deletions Website/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import BaseLayout from "@/app/components/base_layout"
import {inter, meta_info} from "@/utils/common";
import {cookies} from "next/headers";
import {getUsername, inlineTry} from "@/utils/helpers";
import BaseLayout from "@/app/components/base_layout";
import { inter, meta_info } from "@/utils/common";
import { cookies } from "next/headers";
import { getUsername, inlineTry } from "@/utils/helpers";
import React from "react";

export const metadata = meta_info;

/**
* The Layout to use on all pages in the app-directory.
* Effectively defers to BaseLayout with minimal adjustments.
*/
export default function RootLayout({children,}: { children: React.ReactNode }) {
const token = cookies().get('token')?.value;
const username = token ? inlineTry(() => getUsername(token)) : undefined;
export default function RootLayout({ children }: { children: React.ReactNode }) {
const token = cookies().get("token")?.value;
const username = token ? inlineTry(() => getUsername(token)) : undefined;

return (
<html lang="en">
<body className={inter.className}>
<BaseLayout username={username}>
{children}
</BaseLayout>
</body>
</html>
)
return (
<html lang="en">
<body className={inter.className}>
<BaseLayout username={username}>{children}</BaseLayout>
</body>
</html>
);
}


12 changes: 12 additions & 0 deletions Website/src/app/management/components/errorMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** display an error message if there is an error */
export function ErrorMessage({ error }: { error: string | undefined }) {
return (
<>
{error && (
<div className="col-span-8 bg-red-300 border-red-600 text-black rounded p-2 text-center">
Fehler: {error}
</div>
)}
</>
);
}
44 changes: 44 additions & 0 deletions Website/src/app/management/components/exceptionMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { UnauthorizedError } from "@/utils/types";
import { ErrorMessage } from "@/app/management/components/errorMessage";
import Link from "next/link";
import { ReloadButton } from "@/app/components/reloadButton";

/**
* Display a specialized error message for server side exceptions
* @param error The relevant exception thrown.
*/
export function ExceptionMessage({ error }: { error: unknown }) {
const InternalComponent = () => {
if (error instanceof UnauthorizedError) {
return (
<>
<div className={"w-full"}>
<ErrorMessage error={"Ihre Anmeldung ist abgelaufen"} />
</div>
<Link
className={"rounded-full bg-gray-700 px-10 text-white no-a-style w-60 text-center"}
href={"/logout"}
prefetch={false}>
Erneut anmelden
</Link>
</>
);
} else if (error instanceof Error) {
return <ErrorMessage error={error.message} />;
} else if (error instanceof Object) {
return <ErrorMessage error={error.toString()} />;
} else if (typeof error === "string") {
return <ErrorMessage error={error} />;
}
return <ErrorMessage error={"Etwas Unerwartetes ist passiert."} />;
};

return (
<div className={"flex items-center flex-col gap-2"}>
<InternalComponent />
<ReloadButton className={"rounded-full bg-gray-700 px-10 text-white no-a-style w-60 text-center"}>
Erneut versuchen
</ReloadButton>
</div>
);
}
Loading

0 comments on commit a801102

Please sign in to comment.