Remove legacy cards imports and fix publish endpoints

This commit is contained in:
akhisud3195 2025-09-15 14:25:10 +04:00
parent 19da994ac1
commit ba78778a2a
5 changed files with 28 additions and 243 deletions

View file

@ -6,7 +6,7 @@ import { RadioIcon, RedoIcon, UndoIcon, RocketIcon, PenLine, AlertTriangle, Down
import { useParams, useRouter } from "next/navigation";
import { ProgressBar, ProgressStep } from "@/components/ui/progress-bar";
import { useUser } from '@auth0/nextjs-auth0';
import { useState } from "react";
import { useState, useEffect } from "react";
interface TopBarProps {
localProjectName: string;
@ -125,6 +125,16 @@ export function TopBar({
}, 2000); // Reset after 2 seconds
};
// After successful community publish, briefly show success and then close modal
useEffect(() => {
if (communityPublishSuccess) {
const timer = setTimeout(() => {
onShareModalClose();
}, 1200);
return () => clearTimeout(timer);
}
}, [communityPublishSuccess, onShareModalClose]);
const { user } = useUser();
const getUserDisplayName = () => {
@ -827,13 +837,13 @@ export function TopBar({
Cancel
</Button>
<Button
color="primary"
color={communityPublishSuccess ? "success" : "primary"}
onPress={onCommunityPublish}
isLoading={communityPublishing}
isDisabled={!communityData.name.trim() || !communityData.description.trim() || !communityData.category}
className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium"
isDisabled={communityPublishSuccess || !communityData.name.trim() || !communityData.description.trim() || !communityData.category}
className={`${communityPublishSuccess ? 'bg-green-600 hover:bg-green-700' : 'bg-blue-600 hover:bg-blue-700'} px-6 py-2 text-white font-medium`}
>
{communityPublishing ? 'Publishing...' : 'Publish to Community'}
{communityPublishSuccess ? 'Published' : (communityPublishing ? 'Publishing...' : 'Publish to Community')}
</Button>
</ModalFooter>
</ModalContent>

View file

@ -1653,7 +1653,7 @@ export function WorkflowEditor({
setCommunityPublishing(true);
try {
const response = await fetch('/api/community-assistants', {
const response = await fetch('/api/assistant-templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({

View file

@ -16,7 +16,6 @@ import { Tabs, Tab } from "@/components/ui/tabs";
import { Project } from "@/src/entities/models/project";
import { z } from "zod";
import Link from 'next/link';
import { CommunitySection } from '@/components/community/CommunitySection';
import { AssistantSection } from '@/components/common/AssistantSection';
import { UnifiedTemplatesSection } from '@/components/common/UnifiedTemplatesSection';

View file

@ -227,8 +227,8 @@ export function UnifiedTemplatesSection({
</div>
<div className="flex gap-2">
{/* Type Filter Pills */}
<div className="flex gap-1">
{/* Type Filter Segmented Control */}
<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: 'all', label: 'All', count: allTemplates.length },
{ key: 'prebuilt', label: 'Library', count: prebuiltTemplates.length },
@ -237,10 +237,11 @@ export function UnifiedTemplatesSection({
<button
key={key}
onClick={() => setSelectedType(key as any)}
className={`px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
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
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
: 'bg-transparent text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700'
}`}
>
{label} ({count})
@ -253,14 +254,14 @@ export function UnifiedTemplatesSection({
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
className="w-32 px-3 py-1.5 pr-8 border border-gray-200 dark:border-gray-700 rounded-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 appearance-none text-sm"
className="w-44 h-8 px-4 pr-10 border border-gray-300 dark:border-gray-700 rounded-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 appearance-none text-sm hover:bg-gray-50 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
>
<option value="popular">Most Popular</option>
<option value="newest">Newest First</option>
<option value="alphabetical">A-Z</option>
</select>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="pointer-events-none absolute inset-y-0 right-3 flex items-center">
<svg className="w-4 h-4 text-gray-400 -translate-y-[2px]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
@ -278,10 +279,10 @@ export function UnifiedTemplatesSection({
<button
key={category}
onClick={() => toggleCategory(category)}
className={`px-3 py-1 rounded-full text-sm font-medium transition-colors ${
className={`px-3 py-1 rounded-full text-sm font-medium transition-colors border shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 ${
selectedCategories.has(category)
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600'
? 'bg-blue-50 text-blue-700 border-blue-300 dark:bg-blue-900/20 dark:text-blue-300 dark:border-blue-700'
: 'bg-gray-50 text-gray-700 border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700'
}`}
>
{category}

View file

@ -1,225 +0,0 @@
'use client';
import React, { useState, useEffect } from 'react';
import { AssistantSection } from '@/components/common/AssistantSection';
import { useRouter } from 'next/navigation';
import { createProjectFromJsonWithOptions } from '@/app/projects/lib/project-creation-utils';
interface CommunityAssistant {
id: string;
name: string;
description: string;
category: string;
authorId: string;
authorName: string;
authorEmail?: string | null;
isAnonymous: boolean;
workflow: any;
tags: string[];
publishedAt: string;
lastUpdatedAt: string;
downloadCount: number;
likeCount: number;
featured: boolean;
isPublic: boolean;
likes: string[];
copilotPrompt?: string;
thumbnailUrl?: string | null;
}
interface CommunitySectionProps {
onImport?: (assistant: CommunityAssistant) => void;
}
export function CommunitySection({ onImport }: CommunitySectionProps) {
const [assistants, setAssistants] = useState<CommunityAssistant[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [importingIds, setImportingIds] = useState<Set<string>>(new Set());
const [likedIds, setLikedIds] = useState<Set<string>>(new Set());
const router = useRouter();
// Fetch community assistants
const fetchAssistants = async (filters?: { searchQuery: string; selectedCategory: string }) => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams();
if (filters?.searchQuery) params.append('search', filters.searchQuery);
if (filters?.selectedCategory) params.append('category', filters.selectedCategory);
params.append('source', 'community');
const url = `/api/assistant-templates?${params}`;
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch assistants');
const data = await response.json();
setAssistants(data.items || []);
} catch (err) {
console.error('Error fetching assistants:', err);
setError(err instanceof Error ? err.message : 'Failed to load assistants');
} finally {
setLoading(false);
}
};
// Load guest likes from session storage
const loadGuestLikes = () => {
try {
const stored = sessionStorage.getItem('guestLikes');
if (stored) {
const likes = JSON.parse(stored);
setLikedIds(new Set(likes));
}
} catch (err) {
console.error('Error loading guest likes:', err);
}
};
// Save guest likes to session storage
const saveGuestLikes = (likes: Set<string>) => {
try {
sessionStorage.setItem('guestLikes', JSON.stringify(Array.from(likes)));
} catch (err) {
console.error('Error saving guest likes:', err);
}
};
// Get or create consistent guest ID
const getGuestId = () => {
try {
let guestId = sessionStorage.getItem('guestId');
if (!guestId) {
guestId = `guest-${crypto.randomUUID()}`;
sessionStorage.setItem('guestId', guestId);
}
return guestId;
} catch (err) {
// Fallback if sessionStorage is not available
return `guest-${crypto.randomUUID()}`;
}
};
// Handle like toggle
const handleLike = async (item: any) => {
const assistant = assistants.find(a => a.id === item.id);
if (!assistant) return;
try {
const guestId = getGuestId();
const response = await fetch(`/api/assistant-templates/${assistant.id}/like`, {
method: 'POST',
headers: {
'x-guest-id': guestId,
},
});
if (response.ok) {
const data = await response.json();
setLikedIds(prev => {
const newSet = new Set(prev);
if (data.liked) {
newSet.add(assistant.id);
} else {
newSet.delete(assistant.id);
}
saveGuestLikes(newSet);
return newSet;
});
// Update the assistant's like count
setAssistants(prev => prev.map(a =>
a.id === assistant.id
? { ...a, likeCount: data.likeCount }
: a
));
}
} catch (err) {
console.error('Error toggling like:', err);
}
};
// Handle share
const handleShare = (item: any) => {
const assistant = assistants.find(a => a.id === item.id);
if (!assistant) return;
const url = `${window.location.origin}/assistant-templates/${assistant.id}`;
navigator.clipboard.writeText(url).then(() => {
// You could add a toast notification here
console.log('URL copied to clipboard');
}).catch(err => {
console.error('Failed to copy URL:', err);
});
};
// Handle import
const handleImport = async (item: any) => {
const assistant = assistants.find(a => a.id === item.id);
if (!assistant) return;
if (onImport) {
onImport(assistant);
return;
}
setImportingIds(prev => new Set(prev).add(assistant.id));
try {
const response = await fetch(`/api/community-assistants/${assistant.id}`);
if (!response.ok) throw new Error('Failed to fetch assistant details');
const data = await response.json();
await createProjectFromJsonWithOptions({
workflowJson: JSON.stringify(data.workflow),
router,
onSuccess: (projectId) => {
router.push(`/projects/${projectId}/workflow`);
},
onError: (error) => {
console.error('Error creating project:', error);
}
});
} catch (err) {
console.error('Error importing assistant:', err);
// You could add error handling here
} finally {
setImportingIds(prev => {
const newSet = new Set(prev);
newSet.delete(assistant.id);
return newSet;
});
}
};
// Load data on mount
useEffect(() => {
fetchAssistants();
loadGuestLikes();
}, []);
return (
<AssistantSection
title="Community Assistants"
description="Discover and use assistants created by the community."
items={assistants.map(assistant => ({
id: assistant.id,
name: assistant.name,
description: assistant.description,
category: assistant.category,
authorName: assistant.authorName,
isAnonymous: assistant.isAnonymous,
likeCount: assistant.likeCount,
createdAt: assistant.publishedAt,
isLiked: likedIds.has(assistant.id)
}))}
loading={loading}
error={error}
onItemClick={handleImport}
onRetry={() => fetchAssistants()}
loadingItemId={Array.from(importingIds)[0] || null}
emptyMessage="No community assistants available"
onLike={handleLike}
onShare={handleShare}
/>
);
}