Debounced search bar — queries update 300ms after typing stops, clears with ✕ button

Repositories tab — lists all public repos as cards with owner/name link, description, default branch chip, last-updated time; sort by recently updated / newest / name A–Z; prev/next pagination
Users tab — grid of user cards with avatar/initials, username, join date; pagination
Skeleton loaders while fetching, opacity fade during page transitions
All state (tab, sort, query) reflected in the URL so links are shareable
This commit is contained in:
2026-05-07 16:21:35 +02:00
parent 803672a610
commit b624337b4a
4 changed files with 475 additions and 9 deletions
+47
View File
@@ -0,0 +1,47 @@
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'
import { api } from '../client'
const exploreRepoSchema = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
isPrivate: z.boolean(),
defaultBranch: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
ownerId: z.number(),
ownerUsername: z.string(),
ownerAvatarUrl: z.string(),
})
const exploreReposSchema = z.array(exploreRepoSchema)
export type ExploreRepo = z.infer<typeof exploreRepoSchema>
const exploreUserSchema = z.object({
id: z.number(),
username: z.string(),
avatarUrl: z.string(),
createdAt: z.string(),
})
const exploreUsersSchema = z.array(exploreUserSchema)
export type ExploreUser = z.infer<typeof exploreUserSchema>
export function useExploreRepos(q: string, sort: string, offset: number) {
const params = new URLSearchParams({ q, sort, limit: '20', offset: String(offset) })
return useQuery({
queryKey: ['explore', 'repos', q, sort, offset],
queryFn: () =>
api.get<ExploreRepo[]>(`/api/v1/explore/repos?${params}`, exploreReposSchema),
placeholderData: prev => prev,
})
}
export function useExploreUsers(q: string, offset: number) {
const params = new URLSearchParams({ q, limit: '20', offset: String(offset) })
return useQuery({
queryKey: ['explore', 'users', q, offset],
queryFn: () =>
api.get<ExploreUser[]>(`/api/v1/explore/users?${params}`, exploreUsersSchema),
placeholderData: prev => prev,
})
}