mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 22:54:45 -05:00
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:
@@ -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">
|
||||
|
||||
22
src/components/page-header.tsx
Normal file
22
src/components/page-header.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user