feat: Implement data source management and analysis flow, allowing users to add and analyze websites for project opportunities.
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user