diff --git a/app/(root)/dashboard/api-integrations/page.tsx b/app/(root)/dashboard/api-integrations/page.tsx new file mode 100644 index 0000000..9ed0f8f --- /dev/null +++ b/app/(root)/dashboard/api-integrations/page.tsx @@ -0,0 +1,7 @@ +export default function Page(){ + + return( + <> + + ) +} \ No newline at end of file diff --git a/app/api/image_upload/route.ts b/app/api/image_upload/route.ts index 342259f..0bd045f 100644 --- a/app/api/image_upload/route.ts +++ b/app/api/image_upload/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; -import { createClient } from '@/utils/supabase/server'; // Supabase client utility -import { randomUUID } from 'crypto'; // For unique file names +import { createClient } from '@/utils/supabase/server'; +import { randomUUID } from 'crypto'; export async function POST(req: Request) { const supabase = await createClient(); diff --git a/components/Layout/Dashboard/Dashboard.tsx b/components/Layout/Dashboard/Dashboard.tsx index d315fe7..a9fccc1 100644 --- a/components/Layout/Dashboard/Dashboard.tsx +++ b/components/Layout/Dashboard/Dashboard.tsx @@ -61,7 +61,7 @@ export default function Dashboard() { // 3. Empty State: Show No Content Message return ( -
+

No content found.

Start a new search or create your first piece of content! diff --git a/components/Layout/Dashboard/Generate/ApiSenderDialog.tsx b/components/Layout/Dashboard/Generate/ApiSenderDialog.tsx index 4224f7c..7a6128d 100644 --- a/components/Layout/Dashboard/Generate/ApiSenderDialog.tsx +++ b/components/Layout/Dashboard/Generate/ApiSenderDialog.tsx @@ -24,6 +24,7 @@ import { useContent } from '@/context/GenerationContext'; import { cn } from '@/lib/utils'; import { Input } from '@/components/ui/input'; import { motion } from 'framer-motion'; +import { makeRequest } from '@/lib/actions/content.actions'; export const ApiSenderDialog = ({ isOpen, onClose }) => { // Mock hook usage @@ -106,20 +107,13 @@ export const ApiSenderDialog = ({ isOpen, onClose }) => { setErrorDetails(''); setApiResult(null); - // --- Start Mock API Call (Replace this section with actual fetch in a real environment) --- try { - // Implement exponential backoff for resilience (optional but good practice) const maxRetries = 3; let delay = 1000; for (let i = 0; i < maxRetries; i++) { try { - // Simulate successful response - const mockResponse = { - id: 'post-' + Math.floor(Math.random() * 10000), - status: 'published', - url: 'https://preview.yourcms.com/article/' + Math.floor(Math.random() * 10000), - }; + const mockResponse = await makeRequest(apiEndpoint, customBody); setApiResult(mockResponse); setStatus('success'); diff --git a/components/Layout/Dashboard/Generate/ContentRenderer.tsx b/components/Layout/Dashboard/Generate/ContentRenderer.tsx index 8414713..e003dec 100644 --- a/components/Layout/Dashboard/Generate/ContentRenderer.tsx +++ b/components/Layout/Dashboard/Generate/ContentRenderer.tsx @@ -10,6 +10,7 @@ import '@mdxeditor/editor/style.css'; import rehypeRaw from 'rehype-raw'; import { useGlobalState } from '@/context/GlobalStateContext'; import { useContent } from '@/context/GenerationContext'; +import { PromptSelector } from '@/components/Layout/Dashboard/Generate/PromptSelector'; interface ContentRendererProps { isLoading: boolean; @@ -52,7 +53,7 @@ export const Content: React.FC = ({ isLoading }: { isLoadi > {isLoading ? ( - ) : ( + ) : !generatedContent && !isLoading ? () : ( // ARTICLE CONTAINER: This is where we control the content's width. // - max-w-4xl mx-auto: Limits content to a readable width (e.g., 56rem) and centers it. // - flex-col: Ensures Markdown content stacks vertically. diff --git a/components/Layout/Dashboard/Generate/PromptSelector.tsx b/components/Layout/Dashboard/Generate/PromptSelector.tsx new file mode 100644 index 0000000..bdf72d3 --- /dev/null +++ b/components/Layout/Dashboard/Generate/PromptSelector.tsx @@ -0,0 +1,161 @@ +'use client' + +import React, { useState } from 'react'; +import { + Feather, // Placeholder icon for writing + FileText, + Rocket, + Search, + Zap, + CheckCircle, + TrendingUp, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { motion } from 'framer-motion'; +import { useContent } from '@/context/GenerationContext'; + +// --- Prompt Data Structure --- +interface Prompt { + id: string; + title: string; + icon: React.ElementType; + summary: string; + fullPrompt: string; +} + +// --- List of Well-Written Prompts --- +const professionalPrompts: Prompt[] = [ + { + id: 'p-1', + title: 'Executive Summary Generator', + icon: FileText, + summary: 'Analyze a 500-word document and extract the three most critical takeaways, summarizing them into a professional executive overview, suitable for C-level review.', + fullPrompt: 'Analyze the provided text. Identify the three most critical business insights, risks, or opportunities. Summarize these points into a single, concise executive summary paragraph, maintaining a formal and objective tone.', + }, + { + id: 'p-2', + title: 'SEO-Optimized Blog Post', + icon: TrendingUp, + summary: 'Generate a 1000-word blog post on "The Future of Quantum Computing," incorporating a required set of five target keywords naturally throughout the text.', + fullPrompt: 'Draft a 1000-word SEO-optimized blog post titled "The Future of Quantum Computing." The post must include the following keywords: [quantum entanglement], [superposition], [qubit], [d-wave], and [quantum supremacy]. Structure the post with a compelling introduction, three detailed body sections, and a conclusion with a clear call to action.', + }, + { + id: 'p-3', + title: 'Competitive Analysis Report', + icon: Search, + summary: 'Conduct a high-level competitive analysis of three specified companies, detailing their market position, primary product, and unique selling proposition (USP).', + fullPrompt: 'Perform a high-level competitive analysis comparing Company A, Company B, and Company C. For each company, detail their current market share (if known), their flagship product, their core technology advantage, and a concise summary of their Unique Selling Proposition (USP). Present the findings in a clear, comparative table format.', + }, + { + id: 'p-4', + title: 'Viral Social Media Campaign', + icon: Rocket, + summary: 'Design a three-part social media campaign (Twitter thread, LinkedIn post, Instagram caption) to promote a new SaaS feature launch.', + fullPrompt: 'Design a high-impact, three-part social media campaign to promote the launch of a new SaaS feature: "Real-time AI Document Collaboration." Create a compelling, detailed Twitter thread (max 7 tweets), a professional LinkedIn post focused on business value, and a short, engaging Instagram caption with relevant hashtags.', + }, +]; + + +export const PromptSelector = () => { + // State to track the ID of the currently selected prompt + const [selectedPromptId, setSelectedPromptId] = useState(null); + + const { setPrompt } = useContent(); + + // Derive the full details of the selected prompt + const selectedPrompt = professionalPrompts.find(p => p.id === selectedPromptId); + + const handleSelect = (id: string, prompt = '') => { + setSelectedPromptId(id === selectedPromptId ? null : id); + setPrompt(prompt); + }; + + return ( +

+

+ + Prompt Templates Library +

+

+ Select a template to instantly load a professionally written prompt into the editor, or use it as inspiration. +

+ + {/* Prompt Grid Container (Responsive Layout) */} +
+ {professionalPrompts.map((prompt) => { + const isSelected = prompt.id === selectedPromptId; + const IconComponent = prompt.icon; + + return ( + handleSelect(prompt.id, prompt.fullPrompt)} + className={cn( + 'group p-4 rounded-xl border-2 transition-300 cursor-pointer shadow-sm', + 'flex flex-col justify-between min-h-[180px]', + // Default state + 'bg-white border-neutral-200 hover:border-black/50 hover:shadow-md', + // Selected state + isSelected + ? 'border-black bg-black text-white shadow-xl ring-2 ring-offset-2 ring-black/50' + : 'text-neutral-900', + )} + > +
+
+ +
+

+ {prompt.title} +

+
+ +
+

{prompt.summary}

+
+ + {isSelected && ( +
+ Selected +
+ )} +
+ ); + })} +
+ + {/*/!* Selected Prompt Display (for demonstration) *!/*/} + {/*{selectedPrompt && (*/} + {/* */} + {/*

*/} + {/* */} + {/* Active Prompt Text*/} + {/*

*/} + {/*

*/} + {/* {selectedPrompt.fullPrompt}*/} + {/*

*/} + {/*

*/} + {/* *This text is now ready to be loaded into your main input field.**/} + {/*

*/} + {/* */} + {/*)}*/} +
+ ); +}; \ No newline at end of file diff --git a/components/Layout/Dashboard/Generate/content/Renderer.tsx b/components/Layout/Dashboard/Generate/content/Renderer.tsx index 6688e1a..03f2f01 100644 --- a/components/Layout/Dashboard/Generate/content/Renderer.tsx +++ b/components/Layout/Dashboard/Generate/content/Renderer.tsx @@ -11,6 +11,7 @@ import { MarkdownViewer } from '@/components/Layout/Dashboard/Generate/content/M import { Source } from '../RightSidebarPanel'; import EditorLoader from '@/components/Layout/Dashboard/Generate/Editor/EditorLoader'; import { FileText, PenLine, Link as LinkIcon, Code } from 'lucide-react'; +import { PromptSelector } from '@/components/Layout/Dashboard/Generate/PromptSelector'; const Editor = dynamic(() => import('../Editor/Editor'), { // Make sure we turn SSR off @@ -54,6 +55,12 @@ export default function Renderer() { > + + +
@@ -68,6 +75,7 @@ const RendererTabs = () => { [contentRendererTabsState.editor]: PenLine, [contentRendererTabsState.sources]: LinkIcon, [contentRendererTabsState.markdown]: Code, + [contentRendererTabsState.templates]: Code, }; return ( diff --git a/components/Layout/Navigations/Sidebar.tsx b/components/Layout/Navigations/Sidebar.tsx index 0e93b85..392ac1b 100644 --- a/components/Layout/Navigations/Sidebar.tsx +++ b/components/Layout/Navigations/Sidebar.tsx @@ -39,6 +39,7 @@ const navigation = [ title: 'Other', items: [ { href: '/dashboard/explore', icon: Globe, label: 'Explore Tools' }, + { href: '/dashboard/api-integrations', icon: Globe, label: 'API Intgrations' }, ], }, ]; diff --git a/context/GenerationContext.tsx b/context/GenerationContext.tsx index 08130c2..d86b239 100644 --- a/context/GenerationContext.tsx +++ b/context/GenerationContext.tsx @@ -40,6 +40,7 @@ export const contentRendererTabsState = { editor: 'editor', sources: 'sources', markdown: 'markdown', + templates: 'templates' }; // --- 1. Define the Context Interface (What components can access) --- @@ -211,6 +212,7 @@ export function ContextProvider({ children }: { children: ReactNode }) { setIsLoading(true); setGeneratedContent(''); + setPrompt(''); setChatHistory((prev) => [...prev, { id, role: 'user', content: prompt }]); try { diff --git a/lib/actions/content.actions.ts b/lib/actions/content.actions.ts index b5ad389..60cbf95 100644 --- a/lib/actions/content.actions.ts +++ b/lib/actions/content.actions.ts @@ -5,6 +5,7 @@ import { revalidatePath } from 'next/cache'; import { db } from '@/db'; import { contents } from '@/drizzle/schema'; import { and, eq } from 'drizzle-orm'; +import axios from 'axios'; export async function deleteContent(contentId: string) { const supabase = await createClient(); @@ -69,3 +70,13 @@ export async function updateContent(contentId: string, newContent: string) { return { error: 'Failed to update content.' }; } } + +export async function makeRequest(url: string, options: any) { + + try { + return await axios.post(url, options); + }catch (e) { + console.log(e); + throw e; + } +} diff --git a/package.json b/package.json index 76e6cbb..84eb361 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,8 @@ "@types/react-syntax-highlighter": "^15.5.13", "autoprefixer": "^10.4.21", "drizzle-kit": "^0.31.7", - "eslint": "^9", - "eslint-config-next": "16.0.1", + "eslint": "^9.39.1", + "eslint-config-next": "^16.0.5", "eslint-config-prettier": "^10.1.8", "postcss": "^8.5.6", "prettier": "^3.6.2",