From ac625fa55f52c79e99e252ae1f585bbef5014c76 Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Thu, 8 Jan 2026 18:29:03 -0600 Subject: [PATCH] Add conditional UI based on user access level ProjectPage: - Display user's access level badge (Owner/Admin/Write/Read) - Hide "New Package" button for read-only users - Show "Read-only access" text for authenticated read-only users PackagePage: - Hide upload form for read-only users - Show message explaining read-only access - Fetch access level along with package data --- frontend/src/pages/PackagePage.tsx | 59 +++++++++++++++++++----------- frontend/src/pages/ProjectPage.tsx | 35 ++++++++++++++---- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/frontend/src/pages/PackagePage.tsx b/frontend/src/pages/PackagePage.tsx index 2d68ee7..dd5e422 100644 --- a/frontend/src/pages/PackagePage.tsx +++ b/frontend/src/pages/PackagePage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useParams, useSearchParams, useNavigate } from 'react-router-dom'; -import { TagDetail, Package, PaginatedResponse } from '../types'; -import { listTags, getDownloadUrl, getPackage } from '../api'; +import { TagDetail, Package, PaginatedResponse, AccessLevel } from '../types'; +import { listTags, getDownloadUrl, getPackage, getMyProjectAccess } from '../api'; import { Breadcrumb } from '../components/Breadcrumb'; import { Badge } from '../components/Badge'; import { SearchInput } from '../components/SearchInput'; @@ -10,6 +10,7 @@ import { FilterChip, FilterChipGroup } from '../components/FilterChip'; import { DataTable } from '../components/DataTable'; import { Pagination } from '../components/Pagination'; import { DragDropUpload, UploadResult } from '../components/DragDropUpload'; +import { useAuth } from '../contexts/AuthContext'; import './Home.css'; import './PackagePage.css'; @@ -57,6 +58,7 @@ function PackagePage() { const { projectName, packageName } = useParams<{ projectName: string; packageName: string }>(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); + const { user } = useAuth(); const [pkg, setPkg] = useState(null); const [tagsData, setTagsData] = useState | null>(null); @@ -65,6 +67,10 @@ function PackagePage() { const [uploadTag, setUploadTag] = useState(''); const [uploadSuccess, setUploadSuccess] = useState(null); const [artifactIdInput, setArtifactIdInput] = useState(''); + const [accessLevel, setAccessLevel] = useState(null); + + // Derived permissions + const canWrite = accessLevel === 'write' || accessLevel === 'admin'; // Get params from URL const page = parseInt(searchParams.get('page') || '1', 10); @@ -92,12 +98,14 @@ function PackagePage() { try { setLoading(true); - const [pkgData, tagsResult] = await Promise.all([ + const [pkgData, tagsResult, accessResult] = await Promise.all([ getPackage(projectName, packageName), listTags(projectName, packageName, { page, search, sort, order }), + getMyProjectAccess(projectName), ]); setPkg(pkgData); setTagsData(tagsResult); + setAccessLevel(accessResult.access_level); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load data'); @@ -286,28 +294,35 @@ function PackagePage() { {error &&
{error}
} {uploadSuccess &&
{uploadSuccess}
} -
-

Upload Artifact

-
-
- - setUploadTag(e.target.value)} - placeholder="v1.0.0, latest, stable..." + {canWrite ? ( +
+

Upload Artifact

+
+
+ + setUploadTag(e.target.value)} + placeholder="v1.0.0, latest, stable..." + /> +
+
-
-
+ ) : user ? ( +
+

Upload Artifact

+

You have read-only access to this project and cannot upload artifacts.

+
+ ) : null}

Tags / Versions

diff --git a/frontend/src/pages/ProjectPage.tsx b/frontend/src/pages/ProjectPage.tsx index c04bb2b..46d8832 100644 --- a/frontend/src/pages/ProjectPage.tsx +++ b/frontend/src/pages/ProjectPage.tsx @@ -1,13 +1,14 @@ import { useState, useEffect, useCallback } from 'react'; import { useParams, Link, useSearchParams, useNavigate } from 'react-router-dom'; -import { Project, Package, PaginatedResponse } from '../types'; -import { getProject, listPackages, createPackage } from '../api'; +import { Project, Package, PaginatedResponse, AccessLevel } from '../types'; +import { getProject, listPackages, createPackage, getMyProjectAccess } from '../api'; import { Breadcrumb } from '../components/Breadcrumb'; import { Badge } from '../components/Badge'; import { SearchInput } from '../components/SearchInput'; import { SortDropdown, SortOption } from '../components/SortDropdown'; import { FilterChip, FilterChipGroup } from '../components/FilterChip'; import { Pagination } from '../components/Pagination'; +import { useAuth } from '../contexts/AuthContext'; import './Home.css'; const SORT_OPTIONS: SortOption[] = [ @@ -30,6 +31,7 @@ function ProjectPage() { const { projectName } = useParams<{ projectName: string }>(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); + const { user } = useAuth(); const [project, setProject] = useState(null); const [packagesData, setPackagesData] = useState | null>(null); @@ -38,6 +40,11 @@ function ProjectPage() { const [showForm, setShowForm] = useState(false); const [newPackage, setNewPackage] = useState({ name: '', description: '', format: 'generic', platform: 'any' }); const [creating, setCreating] = useState(false); + const [accessLevel, setAccessLevel] = useState(null); + const [isOwner, setIsOwner] = useState(false); + + // Derived permissions + const canWrite = accessLevel === 'write' || accessLevel === 'admin'; // Get params from URL const page = parseInt(searchParams.get('page') || '1', 10); @@ -66,12 +73,15 @@ function ProjectPage() { try { setLoading(true); - const [projectData, packagesResult] = await Promise.all([ + const [projectData, packagesResult, accessResult] = await Promise.all([ getProject(projectName), listPackages(projectName, { page, search, sort, order, format: format || undefined }), + getMyProjectAccess(projectName), ]); setProject(projectData); setPackagesData(packagesResult); + setAccessLevel(accessResult.access_level); + setIsOwner(accessResult.is_owner); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load data'); @@ -159,6 +169,11 @@ function ProjectPage() { {project.is_public ? 'Public' : 'Private'} + {accessLevel && ( + + {isOwner ? 'Owner' : accessLevel.charAt(0).toUpperCase() + accessLevel.slice(1)} + + )}
{project.description &&

{project.description}

}
@@ -169,14 +184,20 @@ function ProjectPage() { by {project.created_by}
- + {canWrite ? ( + + ) : user ? ( + + Read-only access + + ) : null}
{error &&
{error}
} - {showForm && ( + {showForm && canWrite && (

Create New Package