feat: Implement data source management and analysis flow, allowing users to add and analyze websites for project opportunities.

This commit is contained in:
2026-02-03 20:35:03 +00:00
parent 885bbbf954
commit c47614bc66
9 changed files with 587 additions and 54 deletions

View File

@@ -31,11 +31,22 @@ import { api } from "@/convex/_generated/api"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { useProject } from "@/components/project-context"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
const projects = useQuery(api.projects.getProjects);
const { selectedProjectId, setSelectedProjectId } = useProject();
const addDataSource = useMutation(api.dataSources.addDataSource);
const updateDataSourceStatus = useMutation(api.dataSources.updateDataSourceStatus);
const createAnalysis = useMutation(api.analyses.createAnalysis);
const [isAdding, setIsAdding] = React.useState(false);
const [sourceUrl, setSourceUrl] = React.useState("");
const [sourceName, setSourceName] = React.useState("");
const [sourceError, setSourceError] = React.useState<string | null>(null);
const [isSubmittingSource, setIsSubmittingSource] = React.useState(false);
// Set default selected project
React.useEffect(() => {
@@ -66,6 +77,71 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
});
};
const handleAddSource = async () => {
if (!sourceUrl) {
setSourceError("Please enter a URL.");
return;
}
setSourceError(null);
setIsSubmittingSource(true);
try {
const { sourceId, projectId } = await addDataSource({
projectId: selectedProjectId as any,
url: sourceUrl,
name: sourceName || sourceUrl,
type: "website",
});
await updateDataSourceStatus({
dataSourceId: sourceId,
analysisStatus: "pending",
lastError: undefined,
lastAnalyzedAt: undefined,
});
const response = await fetch("/api/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: sourceUrl }),
});
const data = await response.json();
if (!response.ok) {
await updateDataSourceStatus({
dataSourceId: sourceId,
analysisStatus: "failed",
lastError: data.error || "Analysis failed",
lastAnalyzedAt: Date.now(),
});
throw new Error(data.error || "Analysis failed");
}
await createAnalysis({
projectId,
dataSourceId: sourceId,
analysis: data.data,
});
await updateDataSourceStatus({
dataSourceId: sourceId,
analysisStatus: "completed",
lastError: undefined,
lastAnalyzedAt: Date.now(),
});
setSourceUrl("");
setSourceName("");
setIsAdding(false);
} catch (err: any) {
setSourceError(err?.message || "Failed to add source.");
} finally {
setIsSubmittingSource(false);
}
};
return (
<Sidebar variant="inset" {...props}>
<SidebarHeader>
@@ -193,6 +269,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</Label>
</div>
))}
<Button
variant="outline"
size="sm"
onClick={() => setIsAdding(true)}
>
Add Data Source
</Button>
</div>
</SidebarGroupContent>
</SidebarGroup>
@@ -205,6 +288,49 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
avatar: ""
}} />
</SidebarFooter>
<Dialog open={isAdding} onOpenChange={setIsAdding}>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Data Source</DialogTitle>
<DialogDescription>
Add a website to analyze for this project.
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="sourceUrl">Website URL</Label>
<Input
id="sourceUrl"
placeholder="https://example.com"
value={sourceUrl}
onChange={(event) => setSourceUrl(event.target.value)}
disabled={isSubmittingSource}
/>
</div>
<div className="space-y-2">
<Label htmlFor="sourceName">Name (optional)</Label>
<Input
id="sourceName"
placeholder="Product name"
value={sourceName}
onChange={(event) => setSourceName(event.target.value)}
disabled={isSubmittingSource}
/>
</div>
{sourceError && (
<div className="text-sm text-destructive">{sourceError}</div>
)}
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setIsAdding(false)} disabled={isSubmittingSource}>
Cancel
</Button>
<Button onClick={handleAddSource} disabled={isSubmittingSource}>
{isSubmittingSource ? "Analyzing..." : "Add Source"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</Sidebar>
)
}