fix(member): add error handling and toast feedback for missing user profiles
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 46s

This commit is contained in:
Chneemann 2026-08-19 04:05:30 +02:00
parent a457ad19da
commit 8bf1aa41e3
No known key found for this signature in database
2 changed files with 65 additions and 28 deletions

View file

@ -1,38 +1,40 @@
/**
* @file member/[id]/page.tsx
* @description Server component rendering the detailed profile page for a specific member by fetching their user profile data.
* @description Server component rendering the detailed profile page for a specific member by validating the ID format and fetching user profile data.
*/
import { UserService } from "@/services/user.service";
import { notFound } from "next/navigation";
import { redirect } from "next/navigation";
import MemberHeader from "../MemberHeader";
import MemberProfileCard from "./MemberProfileCard";
/**
* Properties for the MemberDetailPage component.
*
* @interface MemberDetailPageProps
* @property {Promise<{ id: string }>} params - Promise containing route context parameters with the target member ID.
*/
interface MemberDetailPageProps {
params: Promise<{ id: string }>;
}
/**
* Renders the member detail page by resolving the route parameter, fetching the user profile by ID,
* and displaying their header and profile card or triggering a 404 page if the member is not found.
* Renders the member detail page by resolving route parameters, validating the member ID syntax,
* fetching profile data, and displaying the header and profile card or redirecting with an error parameter if not found.
*
* @async
* @param {MemberDetailPageProps} props - The component props.
* @param {Object} props - The component props.
* @param {Promise<{ id: string }>} props.params - Promise containing route context parameters with the target member ID.
* @returns {Promise<JSX.Element>} The rendered member detail page component.
*/
export default async function MemberDetailPage({
params,
}: MemberDetailPageProps) {
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const UUID_REGEX =
/^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!UUID_REGEX.test(id)) {
redirect("/member?error=member_not_found");
}
const user = await UserService.findProfileById(id);
if (!user) notFound();
if (!user) {
redirect("/member?error=member_not_found");
}
return (
<div className="space-y-6 max-w-4xl mx-auto pb-12">

View file

@ -5,7 +5,7 @@
"use client";
import { useSearchParams, useRouter } from "next/navigation";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
import { useEffect, useState, useRef, useCallback } from "react";
import { AlertCircle, X } from "lucide-react";
@ -19,6 +19,7 @@ const AUTO_DISMISS_TIME = 5000;
*/
export default function ErrorToast() {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const error = searchParams.get("error");
@ -31,12 +32,19 @@ export default function ErrorToast() {
const animFrameRef = useRef<number | null>(null);
/**
* Dismisses the toast notification and cleans up URL parameters.
* Dismisses the toast notification and cleans up the URL error parameter while preserving the current pathname.
*/
const dismissToast = useCallback(() => {
setIsVisible(false);
router.replace("/dashboard", { scroll: false });
}, [router]);
// Clean up query parameters without forcing a hard redirect to dashboard
const params = new URLSearchParams(searchParams.toString());
params.delete("error");
const query = params.toString();
const newPath = query ? `${pathname}?${query}` : pathname;
router.replace(newPath, { scroll: false });
}, [router, pathname, searchParams]);
// Show a toast message when an error occurs and reset the timer
useEffect(() => {
@ -90,12 +98,39 @@ export default function ErrorToast() {
if (!isVisible || !error) return null;
const message =
error === "unauthorized_edit"
? "You cannot edit tasks that belong to others or are in the trash."
: error === "task_not_found"
? "The requested task does not exist or has been deleted."
: "An unexpected error occurred.";
/**
* Resolves the user-facing error title and detailed message based on the error query parameter.
*
* @returns {{ title: string; message: string }} An object containing the toast header title and body message.
*/
const getErrorInfo = (): { title: string; message: string } => {
switch (error) {
case "unauthorized_edit":
return {
title: "Access Denied",
message:
"You cannot edit tasks that belong to others or are in the trash.",
};
case "task_not_found":
return {
title: "Task Not Found",
message: "The requested task does not exist or has been deleted.",
};
case "member_not_found":
return {
title: "Member Not Found",
message:
"The requested team member does not exist or has been removed.",
};
default:
return {
title: "Error",
message: "An unexpected error occurred.",
};
}
};
const { title, message } = getErrorInfo();
return (
<div
@ -111,7 +146,7 @@ export default function ErrorToast() {
<div className="flex flex-col flex-1">
<div className="flex items-center justify-between">
<h4 className="font-semibold tracking-wider uppercase text-destructive text-sm">
Access Denied
{title}
</h4>
<button
onClick={dismissToast}