From ddfaf74398f8aeb66e919c1a6eb0ac315a4ef569 Mon Sep 17 00:00:00 2001 From: Cyther Date: Fri, 20 Mar 2026 02:21:15 +0400 Subject: [PATCH 1/3] docs: update live demo URL [skip ci] Made-with: Cursor --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44b212d..de780a5 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ A modern, self-hostable GitLab frontend that works with **public repositories ou [![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](Dockerfile) [![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=white)](https://react.dev) [![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org) -[![Live Demo](https://img.shields.io/badge/Live%20Demo-DigitalOcean-0080FF?logo=digitalocean&logoColor=white)](https://glabrowser-f3lru.ondigitalocean.app/) +[![Live Demo](https://img.shields.io/badge/Live%20Demo-DigitalOcean-0080FF?logo=digitalocean&logoColor=white)](https://glabrowser-bchpz.ondigitalocean.app/) -> **🌐 Live demo:** [https://glabrowser-f3lru.ondigitalocean.app/](https://glabrowser-f3lru.ondigitalocean.app/) — browse public GitLab repositories instantly. +> **🌐 Live demo:** [https://glabrowser-bchpz.ondigitalocean.app/](https://glabrowser-bchpz.ondigitalocean.app/) — browse public GitLab repositories instantly. > **Note**: This project is not affiliated with or endorsed by GitLab Inc. It uses the public [GitLab REST API](https://docs.gitlab.com/api/rest/). From cbf70db4048b94784c02e73989e10f8d7cb29840 Mon Sep 17 00:00:00 2001 From: Cyther Date: Fri, 20 Mar 2026 02:24:32 +0400 Subject: [PATCH 2/3] fix(guest): make public repo search work in guest mode The global /search endpoint requires authentication and returned 401 for unauthenticated (guest) requests, silently showing no results. Changes: - In guest mode, the "Projects" scope now falls back to GET /projects?search=...&visibility=public which works without a token. - Issues, Merge Requests, Commits and Code scopes are disabled (greyed out with a lock icon) in guest mode; clicking shows an amber notice with a Sign in link. - Added isError handling so API failures surface a readable message instead of silently showing an empty state. - Empty-state description is contextualised for guest vs authenticated. Made-with: Cursor --- src/pages/Search.tsx | 98 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx index c1f8c6a..1044121 100644 --- a/src/pages/Search.tsx +++ b/src/pages/Search.tsx @@ -1,13 +1,14 @@ import { useState, useEffect } from 'react'; import { Link, useSearchParams } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; -import { Search as SearchIcon, FolderOpen, AlertCircle, GitPullRequest, GitCommit, FileCode } from 'lucide-react'; +import { Search as SearchIcon, FolderOpen, AlertCircle, GitPullRequest, GitCommit, FileCode, Lock } from 'lucide-react'; import { Input } from '../components/ui/input'; import { Button } from '../components/ui/button'; import { Skeleton } from '../components/ui/skeleton'; import { Badge } from '../components/ui/badge'; import EmptyState from '../components/common/EmptyState'; import { useApi } from '../api'; +import { useAuthStore } from '../store/auth'; import type { SearchScope } from '../api/search'; const SCOPES: Array<{ value: SearchScope; label: string; icon: React.ElementType }> = [ @@ -20,6 +21,8 @@ const SCOPES: Array<{ value: SearchScope; label: string; icon: React.ElementType export default function Search() { const api = useApi(); + const { token, user } = useAuthStore(); + const isGuest = token === '' && !user; const [searchParams, setSearchParams] = useSearchParams(); const [query, setQuery] = useState(searchParams.get('q') ?? ''); const [scope, setScope] = useState('projects'); @@ -30,10 +33,36 @@ export default function Search() { setQuery(q); }, [q]); - const { data, isLoading } = useQuery({ - queryKey: ['search', scope, q], - queryFn: () => api.search.global(scope, q, { per_page: 20 }), - enabled: q.length > 1, + // Scopes that require authentication — disabled in guest mode. + const AUTH_REQUIRED_SCOPES: SearchScope[] = ['issues', 'merge_requests', 'commits', 'blobs']; + + const { data, isLoading, isError, error } = useQuery({ + queryKey: ['search', scope, q, isGuest], + queryFn: async () => { + // Guest mode: the global /search endpoint requires authentication. + // For the "projects" scope, fall back to /projects?search=...&visibility=public + // which works without a token. All other scopes are blocked in guest mode. + if (isGuest) { + const result = await api.projects.list({ + search: q, + visibility: 'public', + per_page: 20, + order_by: 'last_activity_at', + }); + return { + items: result.items.map((p) => ({ + id: p.id, + title: p.name_with_namespace, + description: p.description ?? undefined, + web_url: p.web_url, + })), + pagination: result.pagination, + }; + } + return api.search.global(scope, q, { per_page: 20 }); + }, + // In guest mode only the projects scope is supported. + enabled: q.length > 1 && (!isGuest || scope === 'projects'), }); const handleSearch = (e: React.FormEvent) => { @@ -65,22 +94,37 @@ export default function Search() { <> {/* Scope tabs */}
- {SCOPES.map(({ value, label, icon: Icon }) => ( - - ))} + {SCOPES.map(({ value, label, icon: Icon }) => { + const locked = isGuest && AUTH_REQUIRED_SCOPES.includes(value); + return ( + + ); + })}
+ {/* Guest-mode notice for non-project scopes */} + {isGuest && AUTH_REQUIRED_SCOPES.includes(scope) && ( +
+ Searching {scope.replace('_', ' ')} requires a Personal Access Token.{' '} + Sign in to unlock full search. +
+ )} + {/* Results */}
{isLoading ? ( @@ -90,6 +134,16 @@ export default function Search() {
)) + ) : isError ? ( + } + title="Search failed" + description={ + (error as Error)?.message?.includes('401') || (error as Error)?.message?.includes('403') + ? 'This search scope requires authentication. Please sign in.' + : 'Could not reach the GitLab API. Check your connection and try again.' + } + /> ) : !data?.items.length ? ( } @@ -160,7 +214,11 @@ export default function Search() { } title="Search GitLab" - description="Search for projects, issues, merge requests, commits, and code across all your GitLab instances." + description={ + isGuest + ? 'Search for public projects. Sign in to also search issues, merge requests, commits, and code.' + : 'Search for projects, issues, merge requests, commits, and code across all your GitLab instances.' + } /> )} From 2182452df6126213c5084c8d6b18690a368cd00e Mon Sep 17 00:00:00 2001 From: Cyther Date: Fri, 20 Mar 2026 02:27:41 +0400 Subject: [PATCH 3/3] fix(search): resolve TS2339 type errors in guest-mode query result mapping The guest-mode queryFn returned a plain object whose item type didn't include optional GitLabSearchResult fields (state, filename, path, data), causing TypeScript to widen the union and reject property accesses in the render tree. Fix: annotate the mapped items as GitLabSearchResult so the return type is consistently PagedResult in both branches. Made-with: Cursor --- src/pages/Search.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx index 1044121..89dfa2f 100644 --- a/src/pages/Search.tsx +++ b/src/pages/Search.tsx @@ -10,6 +10,7 @@ import EmptyState from '../components/common/EmptyState'; import { useApi } from '../api'; import { useAuthStore } from '../store/auth'; import type { SearchScope } from '../api/search'; +import type { GitLabSearchResult } from '../types/gitlab'; const SCOPES: Array<{ value: SearchScope; label: string; icon: React.ElementType }> = [ { value: 'projects', label: 'Projects', icon: FolderOpen }, @@ -50,7 +51,7 @@ export default function Search() { order_by: 'last_activity_at', }); return { - items: result.items.map((p) => ({ + items: result.items.map((p): GitLabSearchResult => ({ id: p.id, title: p.name_with_namespace, description: p.description ?? undefined,