feat!: add repo searching, only show most relevant repos first
This commit is contained in:
@@ -1,32 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { Button } from 'bits-ui';
|
||||
import { GitBranch, Star, Clock, Import, Search, RefreshCw } from '@lucide/svelte';
|
||||
import { GitBranch, Star, Clock, Import, Search, RefreshCw, LoaderCircle } from '@lucide/svelte';
|
||||
import type { Repository } from './types/repo';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { authClient } from './auth-client';
|
||||
import { apiClient } from './api-client';
|
||||
import NProgress from 'nprogress';
|
||||
|
||||
const session = authClient.useSession();
|
||||
|
||||
const query = createQuery(() => ({
|
||||
queryKey: ['github-repositories'],
|
||||
queryKey: ['recent-repositories'],
|
||||
queryFn: async () => {
|
||||
return await apiClient.request<Repository[]>(`/api/v0/user/${$session.data?.user.id}/repos`);
|
||||
return await apiClient.request<Repository[]>(`/api/v0/user/repos`);
|
||||
},
|
||||
enabled: !!$session.data?.user.id,
|
||||
staleTime: 30000
|
||||
}));
|
||||
|
||||
let searchQuery = $state('');
|
||||
let throttledQuery = $state('');
|
||||
|
||||
$effect(() => {
|
||||
const value = searchQuery;
|
||||
const t = setTimeout(() => {
|
||||
throttledQuery = value;
|
||||
}, 300);
|
||||
return () => clearTimeout(t);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (searchResultsQuery.isFetching || query.isFetching) {
|
||||
NProgress.start();
|
||||
} else {
|
||||
NProgress.done();
|
||||
}
|
||||
});
|
||||
|
||||
const searchResultsQuery = createQuery(() => ({
|
||||
queryKey: ['search-repositories', throttledQuery],
|
||||
queryFn: async () => {
|
||||
return await apiClient.request<Repository[]>(
|
||||
`/api/v0/user/repos/search?q=${encodeURIComponent(throttledQuery)}`
|
||||
);
|
||||
},
|
||||
enabled: !!$session.data?.user.id && throttledQuery.length > 0
|
||||
}));
|
||||
|
||||
let adding = $state<number | null>(null);
|
||||
|
||||
const filteredRepositories = $derived(
|
||||
(query.data ?? []).filter(
|
||||
(repo) =>
|
||||
repo.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
repo.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
const searching = $derived(throttledQuery.length > 0);
|
||||
const repos = $derived(searching ? (searchResultsQuery.data ?? []) : (query.data ?? []));
|
||||
const isPending = $derived(searching ? searchResultsQuery.isPending : query.isPending);
|
||||
|
||||
const handleImport = async (repoId: number) => {
|
||||
adding = repoId;
|
||||
@@ -48,9 +73,11 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<GitBranch class="h-6 w-6 text-gray-400" />
|
||||
<h2 class="text-xl font-semibold text-white">Import Git Repository</h2>
|
||||
<span class="text-xs text-gray-500">repo must be public</span>
|
||||
</div>
|
||||
<Button.Root
|
||||
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-400 transition-colors hover:bg-gray-800 hover:text-white"
|
||||
onclick={() => (searching ? searchResultsQuery.refetch() : query.refetch())}
|
||||
>
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
<span>Refresh</span>
|
||||
@@ -68,18 +95,13 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
{#each filteredRepositories as repo (repo.id)}
|
||||
{#each repos as repo (repo.id)}
|
||||
<div
|
||||
class="group flex items-center justify-between rounded-lg border border-gray-800 bg-gray-800/50 p-4 transition-colors hover:border-gray-700 hover:bg-gray-800"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="truncate font-medium text-white">{repo.name}</h3>
|
||||
{#if repo.isPrivate}
|
||||
<span class="rounded-full border border-gray-600 px-2 py-0.5 text-xs text-gray-400">
|
||||
Private
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if repo.description}
|
||||
<p class="mt-1 truncate text-sm text-gray-400">{repo.description}</p>
|
||||
@@ -106,7 +128,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<Button.Root
|
||||
class="ml-4 flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="ml-4 flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-500 disabled:cursor-default disabled:opacity-50"
|
||||
disabled={adding !== null}
|
||||
onclick={() => handleImport(repo.id)}
|
||||
>
|
||||
@@ -120,9 +142,15 @@
|
||||
</Button.Root>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="py-8 text-center text-gray-500">
|
||||
<p>No repositories found matching "{searchQuery}"</p>
|
||||
</div>
|
||||
{#if isPending}
|
||||
<div class="flex justify-center">
|
||||
<LoaderCircle class="animate-spin" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="py-8 text-center text-gray-500">
|
||||
<p>No repositories found matching "{searchQuery}"</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,15 +5,16 @@ import * as authSchema from './db/auth-schema';
|
||||
import { jwt } from 'better-auth/plugins';
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg',
|
||||
schema: { ...authSchema }
|
||||
}),
|
||||
plugins: [jwt()],
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GH_CLIENT_ID!,
|
||||
clientSecret: process.env.GH_CLIENT_SECRET!
|
||||
}
|
||||
}
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg',
|
||||
schema: { ...authSchema }
|
||||
}),
|
||||
plugins: [jwt()],
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GH_CLIENT_ID!,
|
||||
clientSecret: process.env.GH_CLIENT_SECRET!,
|
||||
scope: ["repo"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user