Update DB calls to be standardized paginated

This commit is contained in:
akhisud3195 2025-09-15 17:24:46 +04:00
parent 6834228f43
commit 48c27b3303
2 changed files with 79 additions and 19 deletions

View file

@ -1,7 +1,7 @@
'use client';
import { useState, useRef, useEffect } from "react";
import { listTemplates, listProjects } from "@/app/actions/project.actions";
import { listProjects } from "@/app/actions/project.actions";
import { createProjectWithOptions, createProjectFromJsonWithOptions, createProjectFromTemplate } from "../lib/project-creation-utils";
import { useRouter, useSearchParams } from 'next/navigation';
import clsx from 'clsx';
@ -50,12 +50,17 @@ export function BuildAssistantSection() {
const [promptError, setPromptError] = useState<string | null>(null);
const [importLoading, setImportLoading] = useState(false);
const [importError, setImportError] = useState<string | null>(null);
// Library templates (paginated)
const [templates, setTemplates] = useState<any[]>([]);
const [templatesLoading, setTemplatesLoading] = useState(false);
const [templatesError, setTemplatesError] = useState<string | null>(null);
const [templatesCursor, setTemplatesCursor] = useState<string | null>(null);
// Community templates (paginated)
const [communityTemplates, setCommunityTemplates] = useState<any[]>([]);
const [communityTemplatesLoading, setCommunityTemplatesLoading] = useState(false);
const [communityTemplatesError, setCommunityTemplatesError] = useState<string | null>(null);
const [communityCursor, setCommunityCursor] = useState<string | null>(null);
const [projects, setProjects] = useState<z.infer<typeof Project>[]>([]);
const [projectsLoading, setProjectsLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
@ -93,28 +98,36 @@ export function BuildAssistantSection() {
return Array.from(uniqueToolsMap.values()).filter(tool => tool.logo); // Only show tools with logos like ToolkitCard
};
const fetchTemplates = async () => {
const fetchLibraryTemplatesPage = async (cursor?: string | null, limit: number = 20) => {
setTemplatesLoading(true);
setTemplatesError(null);
try {
const templatesArray = await listTemplates();
setTemplates(templatesArray);
const params = new URLSearchParams({ source: 'library', limit: String(limit) });
if (cursor) params.set('cursor', cursor);
const response = await fetch(`/api/assistant-templates?${params.toString()}`);
if (!response.ok) throw new Error('Failed to fetch library templates');
const data = await response.json();
setTemplates(prev => cursor ? [...prev, ...data.items] : data.items);
setTemplatesCursor(data.nextCursor || null);
} catch (error) {
console.error('Error fetching templates:', error);
console.error('Error fetching library templates:', error);
setTemplatesError(error instanceof Error ? error.message : 'Failed to load templates');
} finally {
setTemplatesLoading(false);
}
};
const fetchCommunityTemplates = async () => {
const fetchCommunityTemplatesPage = async (cursor?: string | null, limit: number = 20) => {
setCommunityTemplatesLoading(true);
setCommunityTemplatesError(null);
try {
const response = await fetch('/api/assistant-templates?source=community');
const params = new URLSearchParams({ source: 'community', limit: String(limit) });
if (cursor) params.set('cursor', cursor);
const response = await fetch(`/api/assistant-templates?${params.toString()}`);
if (!response.ok) throw new Error('Failed to fetch community templates');
const data = await response.json();
setCommunityTemplates(data.items || []);
setCommunityTemplates(prev => cursor ? [...prev, ...data.items] : data.items);
setCommunityCursor(data.nextCursor || null);
} catch (error) {
console.error('Error fetching community templates:', error);
setCommunityTemplatesError(error instanceof Error ? error.message : 'Failed to load community templates');
@ -123,6 +136,30 @@ export function BuildAssistantSection() {
}
};
// Ensure we have at least `targetCount` items loaded for a given type
const ensureTemplatesLoaded = async (type: 'prebuilt' | 'community', targetCount: number) => {
const current = type === 'prebuilt' ? templates.length : communityTemplates.length;
const cursor = type === 'prebuilt' ? templatesCursor : communityCursor;
if (current >= targetCount) return;
// Fetch pages until we meet or exceed target or run out of pages
// Use page size equal to remaining needed but capped reasonably
let needed = targetCount - current;
let nextCursor = cursor;
while (needed > 0 && (nextCursor !== null || current === 0)) {
const pageSize = Math.min(Math.max(needed, 12), 30);
if (type === 'prebuilt') {
await fetchLibraryTemplatesPage(nextCursor, pageSize);
nextCursor = templatesCursor; // will be updated by set state; slight lag acceptable
} else {
await fetchCommunityTemplatesPage(nextCursor, pageSize);
nextCursor = communityCursor;
}
// Update needed based on latest lengths
needed = targetCount - (type === 'prebuilt' ? templates.length : communityTemplates.length);
if (nextCursor === null) break;
}
};
// Handle template selection
const handleTemplateSelect = async (template: any) => {
// Show a small non-blocking spinner on the clicked card
@ -253,9 +290,9 @@ export function BuildAssistantSection() {
};
useEffect(() => {
fetchTemplates();
fetchCommunityTemplates();
// Load initial library templates to fill 4 rows x up to 3 columns ≈ 12
fetchProjects();
ensureTemplatesLoaded('prebuilt', 12);
}, []);
// Handle URL parameters for auto-creation and direct redirect to build view
@ -624,8 +661,8 @@ export function BuildAssistantSection() {
error={templatesError || communityTemplatesError}
onTemplateClick={handleTemplateSelect}
onRetry={() => {
fetchTemplates();
fetchCommunityTemplates();
fetchLibraryTemplatesPage(undefined, 12);
fetchCommunityTemplatesPage(undefined, 12);
}}
loadingItemId={loadingTemplateId}
onLike={handleTemplateLike}
@ -643,6 +680,12 @@ export function BuildAssistantSection() {
}
}}
getUniqueTools={getUniqueTools}
onLoadMore={async (type, target) => {
await ensureTemplatesLoaded(type, target);
}}
onTypeChange={async (type, target) => {
await ensureTemplatesLoaded(type, target);
}}
/>
</div>
)}

View file

@ -39,6 +39,8 @@ interface UnifiedTemplatesSectionProps {
onShare?: (item: TemplateItem) => void;
onDelete?: (item: TemplateItem) => void;
getUniqueTools?: (item: TemplateItem) => Array<{ name: string; logo?: string }>;
onLoadMore?: (type: 'prebuilt' | 'community', targetCount: number) => Promise<void> | void;
onTypeChange?: (type: 'prebuilt' | 'community', targetCount: number) => Promise<void> | void;
}
export function UnifiedTemplatesSection({
@ -52,7 +54,9 @@ export function UnifiedTemplatesSection({
onLike,
onShare,
onDelete,
getUniqueTools
getUniqueTools,
onLoadMore,
onTypeChange,
}: UnifiedTemplatesSectionProps) {
const [searchQuery, setSearchQuery] = useState('');
const [selectedType, setSelectedType] = useState<'prebuilt' | 'community'>('prebuilt');
@ -278,12 +282,19 @@ export function UnifiedTemplatesSection({
{/* Type Filter Segmented Control (Library | Community) */}
<div className="flex gap-0.5 items-center h-8 rounded-full border border-gray-200 dark:border-gray-700 p-0 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
{[
{ key: 'prebuilt', label: 'Library', count: prebuiltTemplates.length },
{ key: 'community', label: 'Community', count: communityTemplates.length }
].map(({ key, label, count }) => (
{ key: 'prebuilt', label: 'Library' },
{ key: 'community', label: 'Community' }
].map(({ key, label }) => (
<button
key={key}
onClick={() => setSelectedType(key as any)}
onClick={async () => {
const newType = key as 'prebuilt' | 'community';
const target = rowsShown * itemsPerRow;
if (onTypeChange) {
await onTypeChange(newType, target);
}
setSelectedType(newType);
}}
aria-pressed={selectedType === key}
className={`inline-flex items-center h-8 px-2.5 rounded-full text-[13px] font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 ${
selectedType === key
@ -291,7 +302,7 @@ export function UnifiedTemplatesSection({
: 'bg-transparent text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700'
}`}
>
{label} ({count})
{label}
</button>
))}
</div>
@ -404,7 +415,13 @@ export function UnifiedTemplatesSection({
{hasMore && (
<div className="flex items-center justify-center pt-2">
<button
onClick={() => setRowsShown(prev => prev + 4)}
onClick={async () => {
const target = (rowsShown + 4) * itemsPerRow;
if (onLoadMore) {
await onLoadMore(selectedType, target);
}
setRowsShown(prev => prev + 4);
}}
className="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium underline-offset-2 hover:underline"
>
View more