diff --git a/backend/app/seed.py b/backend/app/seed.py index fdd3c07..9a18e66 100644 --- a/backend/app/seed.py +++ b/backend/app/seed.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Session from .models import Project, Package, Artifact, Tag, Upload, PackageVersion, ArtifactDependency, Team, TeamMembership, User from .storage import get_storage +from .auth import hash_password logger = logging.getLogger(__name__) @@ -176,6 +177,53 @@ def seed_database(db: Session) -> None: logger.info(f"Created team: {demo_team.name} ({demo_team.slug})") + # Create test users with various roles + test_users = [ + {"username": "alice", "email": "alice@example.com", "role": "admin"}, + {"username": "bob", "email": "bob@example.com", "role": "admin"}, + {"username": "charlie", "email": "charlie@example.com", "role": "member"}, + {"username": "diana", "email": "diana@example.com", "role": "member"}, + {"username": "eve", "email": "eve@example.com", "role": "member"}, + {"username": "frank", "email": None, "role": "member"}, + ] + + for user_data in test_users: + # Check if user already exists + existing_user = db.query(User).filter(User.username == user_data["username"]).first() + if existing_user: + test_user = existing_user + else: + # Create the user with password same as username + test_user = User( + username=user_data["username"], + email=user_data["email"], + password_hash=hash_password(user_data["username"]), + is_admin=False, + is_active=True, + must_change_password=False, + ) + db.add(test_user) + db.flush() + logger.info(f"Created test user: {user_data['username']}") + + # Add to demo team with specified role + existing_membership = db.query(TeamMembership).filter( + TeamMembership.team_id == demo_team.id, + TeamMembership.user_id == test_user.id, + ).first() + + if not existing_membership: + membership = TeamMembership( + team_id=demo_team.id, + user_id=test_user.id, + role=user_data["role"], + invited_by=team_owner_username, + ) + db.add(membership) + logger.info(f"Added {user_data['username']} to {demo_team.slug} as {user_data['role']}") + + db.flush() + # Create projects and packages project_map = {} package_map = {} diff --git a/frontend/src/pages/TeamMembersPage.css b/frontend/src/pages/TeamMembersPage.css index 47987db..7d545b1 100644 --- a/frontend/src/pages/TeamMembersPage.css +++ b/frontend/src/pages/TeamMembersPage.css @@ -16,34 +16,11 @@ font-size: 1.75rem; } -/* Members list */ -.members-list { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.member-card { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - gap: 1rem; -} - -.member-card.current-user { - background: var(--color-primary-bg); - border-color: var(--color-primary-border, var(--color-border)); -} - -.member-info { +/* Member cell in table */ +.member-cell { display: flex; align-items: center; gap: 0.75rem; - min-width: 0; } .member-avatar { @@ -87,11 +64,8 @@ white-space: nowrap; } -.member-actions { - display: flex; - align-items: center; - gap: 0.5rem; - flex-shrink: 0; +.text-muted { + color: var(--color-text-muted); } .role-select { diff --git a/frontend/src/pages/TeamMembersPage.tsx b/frontend/src/pages/TeamMembersPage.tsx index e92cd42..a03e880 100644 --- a/frontend/src/pages/TeamMembersPage.tsx +++ b/frontend/src/pages/TeamMembersPage.tsx @@ -11,6 +11,7 @@ import { import { useAuth } from '../contexts/AuthContext'; import { Badge } from '../components/Badge'; import { Breadcrumb } from '../components/Breadcrumb'; +import { DataTable } from '../components/DataTable'; import { UserAutocomplete } from '../components/UserAutocomplete'; import './TeamMembersPage.css'; @@ -201,34 +202,49 @@ function TeamMembersPage() { )} -
- {members.map(member => { - const isCurrentUser = user?.username === member.username; - const canModify = isAdmin && !isCurrentUser && (isOwner || member.role !== 'owner'); + member.id} + emptyMessage="No members in this team yet." + columns={[ + { + key: 'member', + header: 'Member', + render: (member) => { + const isCurrentUser = user?.username === member.username; + return ( +
+
+ {member.username.charAt(0).toUpperCase()} +
+
+ + {member.username} + {isCurrentUser && (you)} + + {member.email && ( + {member.email} + )} +
+
+ ); + }, + }, + { + key: 'role', + header: 'Role', + render: (member) => { + const isCurrentUser = user?.username === member.username; + const canModify = isAdmin && !isCurrentUser && (isOwner || member.role !== 'owner'); - return ( -
-
-
- {member.username.charAt(0).toUpperCase()} -
-
- - {member.username} - {isCurrentUser && (you)} - - {member.email && ( - {member.email} - )} -
-
-
- {canModify ? ( + if (canModify) { + return ( - ) : ( - - {member.role} - - )} - {canModify && ( - - )} -
-
- ); - })} -
+ ); + } + return ( + + {member.role} + + ); + }, + }, + { + key: 'joined', + header: 'Joined', + render: (member) => ( + + {new Date(member.created_at).toLocaleDateString()} + + ), + }, + ...(isAdmin ? [{ + key: 'actions', + header: '', + render: (member: TeamMember) => { + const isCurrentUser = user?.username === member.username; + const canModify = isAdmin && !isCurrentUser && (isOwner || member.role !== 'owner'); + + if (!canModify) return null; + + return ( + + ); + }, + }] : []), + ]} + /> ); } diff --git a/frontend/src/pages/TeamsPage.css b/frontend/src/pages/TeamsPage.css index 56a0428..671e8d5 100644 --- a/frontend/src/pages/TeamsPage.css +++ b/frontend/src/pages/TeamsPage.css @@ -13,37 +13,12 @@ gap: 1rem; } -.teams-header__content { - display: flex; - align-items: center; - gap: 1.5rem; -} - -.teams-header__content h1 { +.teams-header h1 { margin: 0; font-size: 1.5rem; font-weight: 600; } -.teams-header__stats { - display: flex; - align-items: center; - gap: 1rem; - color: var(--color-text-muted); - font-size: 0.875rem; -} - -.teams-header__stats span { - display: flex; - align-items: center; - gap: 0.375rem; -} - -.teams-header__stats .stat-value { - font-weight: 600; - color: var(--color-text); -} - /* Search */ .teams-search { position: relative; diff --git a/frontend/src/pages/TeamsPage.tsx b/frontend/src/pages/TeamsPage.tsx index 9a08dc4..9dc8651 100644 --- a/frontend/src/pages/TeamsPage.tsx +++ b/frontend/src/pages/TeamsPage.tsx @@ -79,10 +79,7 @@ function TeamsPage() { (team.description?.toLowerCase().includes(searchQuery.toLowerCase())) ) || []; - // Stats const totalTeams = teamsData?.items.length || 0; - const totalProjects = teamsData?.items.reduce((sum, t) => sum + t.project_count, 0) || 0; - const ownedTeams = teamsData?.items.filter(t => t.user_role === 'owner').length || 0; const roleConfig: Record = { owner: { variant: 'success', label: 'Owner' }, @@ -114,16 +111,7 @@ function TeamsPage() {
{/* Header */}
-
-

Teams

- {!loading && totalTeams > 0 && ( -
- {totalTeams} teams - {ownedTeams} owned - {totalProjects} projects -
- )} -
+

Teams