feat: Enhance user management and UI components

- Updated user API routes to include imageUrl for better user representation.
- Added @radix-ui/react-toast dependency for improved user notifications.
- Refactored dashboard and studies components to incorporate new user fields and loading states.
- Enhanced the layout and structure of the dashboard, studies, and participants pages for better user experience.
- Implemented a dialog for adding participants, improving the participant management workflow.
- Updated breadcrumb navigation to reflect the current study context more accurately.
- Cleaned up unused imports and optimized component rendering for performance.
This commit is contained in:
2024-12-05 13:21:33 -05:00
parent 207f4d7fb8
commit 80171b2d70
17 changed files with 712 additions and 454 deletions

View File

@@ -18,22 +18,25 @@ export function Breadcrumb() {
const items: BreadcrumbItem[] = [{ label: 'Dashboard', href: '/dashboard' }];
const path = pathname.split('/').filter(Boolean);
// Handle studies list page
if (path[1] === 'studies' && !activeStudy) {
items.push({ label: 'Studies', href: '/dashboard/studies' });
if (path[2] === 'new') {
items.push({ label: 'New Study' });
}
// Handle root dashboard
if (path.length === 1 && path[0] === 'dashboard') {
return items;
}
// Handle active study pages
if (activeStudy) {
items.push({
label: 'Studies',
href: '/dashboard/studies'
});
// Handle studies list page
if (path[1] === 'studies') {
items.push({ label: 'Studies', href: '/dashboard/studies' });
if (path[2] === 'new') {
items.push({ label: 'New Study' });
return items;
}
if (!activeStudy) {
return items;
}
// Handle active study pages
items.push({
label: activeStudy.title,
href: `/dashboard/studies/${activeStudy.id}`
@@ -56,6 +59,25 @@ export function Breadcrumb() {
});
}
}
return items;
}
// Handle participants page
if (path[1] === 'participants') {
items.push({ label: 'Participants', href: '/dashboard/participants' });
return items;
}
// Handle settings page
if (path[1] === 'settings') {
items.push({ label: 'Settings', href: '/dashboard/settings' });
return items;
}
// Handle profile page
if (path[1] === 'profile') {
items.push({ label: 'Profile', href: '/dashboard/profile' });
return items;
}
return items;
@@ -63,7 +85,8 @@ export function Breadcrumb() {
const breadcrumbs = getBreadcrumbs();
if (breadcrumbs.length <= 1) return null;
// Always show breadcrumbs on dashboard pages
if (breadcrumbs.length <= 1 && !pathname.startsWith('/dashboard')) return null;
return (
<div className="flex items-center space-x-2 text-sm text-muted-foreground mb-6">

View File

@@ -0,0 +1,22 @@
interface PageHeaderProps {
title: string;
description: string;
children?: React.ReactNode;
}
export function PageHeader({ title, description, children }: PageHeaderProps) {
return (
<div className="mb-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
<p className="text-sm text-muted-foreground mt-1">
{description}
</p>
</div>
{children}
</div>
</div>
);
}

View File

@@ -38,6 +38,14 @@ const getNavItems = (studyId?: number) => [
exact: true,
requiresStudy: false
},
{
name: "Studies",
href: "/dashboard/studies",
icon: FolderIcon,
exact: true,
requiresStudy: false,
hideWithStudy: true
},
{
name: "Participants",
href: `/dashboard/studies/${studyId}/participants`,
@@ -84,7 +92,7 @@ export function Sidebar() {
const navItems = getNavItems(activeStudy?.id)
const visibleNavItems = activeStudy
? navItems
? navItems.filter(item => !item.hideWithStudy)
: navItems.filter(item => !item.requiresStudy)
const isActiveRoute = (item: { href: string, exact?: boolean, baseRoute?: string }) => {

View File

@@ -40,6 +40,7 @@ interface User {
email: string;
name: string | null;
roles: Array<{ id: number; name: string }>;
imageUrl: string;
}
interface Invitation {
@@ -211,17 +212,17 @@ export function UsersTab({ studyId, permissions }: UsersTabProps) {
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold tracking-tight">Team Members</h2>
{canManageRoles && <InviteUserDialog studyId={studyId} onInviteSent={fetchInvitations} />}
</div>
<Card>
<CardHeader>
<CardTitle>Study Members</CardTitle>
<CardDescription>
Manage users and their roles in this study
</CardDescription>
<CardHeader className="flex flex-col justify-between">
<div className="flex justify-between items-center">
<div>
<CardTitle>Study Members</CardTitle>
<CardDescription>
Manage users and their roles in this study
</CardDescription>
</div>
{canManageRoles && <InviteUserDialog studyId={studyId} onInviteSent={fetchInvitations} />}
</div>
</CardHeader>
<CardContent>
<Table>
@@ -240,6 +241,7 @@ export function UsersTab({ studyId, permissions }: UsersTabProps) {
user={{
name: formatName(user),
email: user.email,
imageUrl: user.imageUrl,
}}
/>
<span>{formatName(user)}</span>

View File

@@ -1,9 +1,10 @@
import { Avatar, AvatarFallback } from "~/components/ui/avatar";
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
interface UserAvatarProps {
user: {
name?: string | null;
email: string;
imageUrl?: string | null;
};
className?: string;
}
@@ -21,6 +22,7 @@ export function UserAvatar({ user, className }: UserAvatarProps) {
return (
<Avatar className={className}>
{user.imageUrl && <AvatarImage src={user.imageUrl} alt={user.name || user.email} />}
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
);