"use client";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { IUser } from "@/interfaces/database.interfaces";
import { UserType } from "@/interfaces/userpage.interfaces";
import { useQuery } from 'react-query';
import { Types } from "mongoose";
import RoleSelector from "@/components/User Page/RoleSelector";
async function getUsers(): Promise<UserType[] | undefined> {
const res = await fetch('/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await res.json();
return data;
}
async function getUserDetails(id:Types.ObjectId):Promise<IUser | undefined>{
const res = await fetch(`/api/users/${id}`,{
method: 'GET',
headers:{
'Content-Type':'application/json'
}
});
if (!res.ok) {
return undefined;
}
const data = await res.json();
return data;
}
const User2 = () => {
// get User
const roles = ['User', 'Maintainer', 'Admin'];
const { data:Users, status } = useQuery('users', getUsers);
const [openUserId, setOpenUserId] = useState<Types.ObjectId | null>(null);
const handleToggle = (userId: Types.ObjectId) => {
if (openUserId !== userId) {
setOpenUserId(userId);
} else if (openUserId === userId) {
setOpenUserId(null);
}
};
if (status === 'loading') {
return <div>Loading...</div>;
}
if (status === 'error') {
return <div>Error fetching data</div>;
}
// let's not use hooks for now
// Pagination
// Search somehow?
// Filter somehow?
// Define Roles here
// Make sure: Less definitions in the loops
return (
<div className="background background-light900_dark300 h-auto">
{ /* Restricted Access component */}
<h1 className="h1-bold text-dark100_light900">Users</h1>
{/* <UserSearch /> */}
<section className="mt-7 border border-gray-200 p-4 shadow-md shadow-gray-300 dark:border-dark-400 dark:shadow dark:shadow-gray-500">
{
Users?.map((user) => {
return (
<div key={`${user._id}`} className="border-b-2 border-gray-300">
<div className="flex space-x-40 p-4 max-mmd:items-start" onClick={()=> handleToggle(user._id)}>
<div className="flex flex-1 max-mmd:flex-col">
<p className="text-dark500_light700 font-medium">
{user.display_name}
</p>
<p className="user-info text-dark500_light700 text-right font-medium max-mmd:text-left">
{user.email}
</p>
</div>
<motion.span
className={`${openUserId === user._id ? "rotate-180" : "rotate-0"} max-xs:hidden`}
initial={{ rotate: 0 }}
animate={{ rotate: openUserId === user._id ? 180 : 0 }}
transition={{ duration: 0.2 }}
>
<svg
className="size-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M5.293 9.293a1 1 0 011.414 0L10 12.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</motion.span>
</div>
<AnimatePresence>
{
openUserId === user._id &&
// fetch some data first and than render the component
(
<UserDetails userId ={user._id} roles={roles}/>
)
}
</AnimatePresence>
</div>
);
})
}
</section>
{/* <UserPagination /> */}
</div>
);
};
const UserDetails = ({userId,roles}:{userId:Types.ObjectId,roles:string[]}) => {
const { data, status } = useQuery(['DetailsUser', userId], () => getUserDetails(userId), {
enabled: !!userId,
staleTime: Infinity, // Cache the data indefinitely
});
if (status === 'loading') {
return <div>Loading...</div>;
}
if(status === 'error'){
return <div>Error fetching data</div>;
}
console.log(data);
console.log(data?.role)
return(
<motion.div
initial="collapsed"
animate="open"
exit="collapsed"
variants={{
open: { opacity: 1, height: "auto" },
collapsed: { opacity: 0, height: 0 },
}}
transition={{ duration: 0.4, ease: "easeInOut" }}
className="relative overflow-hidden">
<div className="relative grid grid-cols-1 justify-items-start px-4 pb-7 md:items-baseline mmd:grid-cols-2 mmd:grid-rows-1">
<div className="absolute left-3 top-6">
<RoleSelector userId={data?._id} userRole={data?.role} roles={roles} />
</div>
<button
className="absolute bottom-5 right-10 rounded bg-red-600 px-3 py-1 font-bold text-white hover:bg-red-700 md:px-4 md:py-2 xl:px-7 xl:py-2"
>
Delete
</button>
<div className="">
<div className="mt-20">
<h4 className="text-dark100_light900">Card number</h4>
<p className="font-extralight text-gray-400 dark:text-gray-600">
{data?.card_number}
</p>
</div>
<div className="mb-5 mt-6">
<h4 className="text-dark100_light900">Card ID</h4>
<p className="font-extralight text-gray-400 dark:text-gray-600">
{data?.card_id}
</p>
</div>
</div>
</div>
</motion.div>
)
}
export default User2;
The above code is rendering a users page of a NextJS application. The issue lies within the UserDetails commponent function, which is fetching a user from the database, based on their user id. After fetching it is unable to access the props of the fetched data.
The data that I am trying to fetch in the UserDetails component is based on the following interface:
export interface IUser {
_id:Types.ObjectId,
azure_id: string;
email: string;
first_name: string;
last_name: string;
display_name: string;
card_id: string;
card_number: string;
role: string;
permissions?: Types.ObjectId[];
}
When running console log for debugging:
console.log(data);
console.log(data?.role)
I get the following response:
I see a log where data
is defined, and then a log correctly showing data.role
as undefined. data
has user
and message
properties. I assume you meant to log data.user.role
instead. It looks like all the properties you are trying to access are on data.user
and not on the root data
reference. Based on the getUserDetails
return type it appears you should "unpack"/return data.user
from getUserDetails
.
async function getUserDetails(id: Types.ObjectId): Promise<IUser | undefined>{
try {
const res = await fetch(`/api/users/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const { user } = await res.json();
return user; // <-- return the res.data.user IUser object
} catch {
return undefined;
}
}