feat: Implement a new dashboard layout with sidebar, introduce project and data source management, and add various UI components.
This commit is contained in:
4
convex/_generated/api.d.ts
vendored
4
convex/_generated/api.d.ts
vendored
@@ -9,7 +9,9 @@
|
||||
*/
|
||||
|
||||
import type * as auth from "../auth.js";
|
||||
import type * as dataSources from "../dataSources.js";
|
||||
import type * as http from "../http.js";
|
||||
import type * as projects from "../projects.js";
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
@@ -19,7 +21,9 @@ import type {
|
||||
|
||||
declare const fullApi: ApiFromModules<{
|
||||
auth: typeof auth;
|
||||
dataSources: typeof dataSources;
|
||||
http: typeof http;
|
||||
projects: typeof projects;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
||||
3
convex/auth.config.ts
Normal file
3
convex/auth.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
providers: [],
|
||||
};
|
||||
75
convex/dataSources.ts
Normal file
75
convex/dataSources.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
|
||||
export const getProjectDataSources = query({
|
||||
args: { projectId: v.id("projects") },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) return [];
|
||||
|
||||
// Verify project ownership
|
||||
const project = await ctx.db.get(args.projectId);
|
||||
if (!project || project.userId !== userId) return [];
|
||||
|
||||
return await ctx.db
|
||||
.query("dataSources")
|
||||
.filter((q) => q.eq(q.field("projectId"), args.projectId))
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
export const addDataSource = mutation({
|
||||
args: {
|
||||
projectId: v.optional(v.id("projects")), // Optional, if not provided, use default
|
||||
url: v.string(),
|
||||
name: v.string(),
|
||||
type: v.literal("website"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) throw new Error("Unauthorized");
|
||||
|
||||
let projectId = args.projectId;
|
||||
|
||||
// Use default project if not provided
|
||||
if (!projectId) {
|
||||
const defaultProject = await ctx.db
|
||||
.query("projects")
|
||||
.filter((q) => q.and(q.eq(q.field("userId"), userId), q.eq(q.field("isDefault"), true)))
|
||||
.first();
|
||||
|
||||
if (!defaultProject) {
|
||||
// Create a default project if none exists (First onboarding)
|
||||
projectId = await ctx.db.insert("projects", {
|
||||
userId,
|
||||
name: "My Project",
|
||||
isDefault: true,
|
||||
dorkingConfig: { selectedSourceIds: [] },
|
||||
});
|
||||
} else {
|
||||
projectId = defaultProject._id;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceId = await ctx.db.insert("dataSources", {
|
||||
projectId: projectId!, // Assert exists
|
||||
type: args.type,
|
||||
url: args.url,
|
||||
name: args.name,
|
||||
analysisStatus: "pending",
|
||||
// analysisResults not set initially
|
||||
});
|
||||
|
||||
// Auto-select this source in the project config
|
||||
const project = await ctx.db.get(projectId!);
|
||||
if (project) {
|
||||
const currentSelected = project.dorkingConfig.selectedSourceIds;
|
||||
await ctx.db.patch(projectId!, {
|
||||
dorkingConfig: { selectedSourceIds: [...currentSelected, sourceId] }
|
||||
});
|
||||
}
|
||||
|
||||
return sourceId;
|
||||
},
|
||||
});
|
||||
70
convex/projects.ts
Normal file
70
convex/projects.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
|
||||
export const getProjects = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) return [];
|
||||
return await ctx.db
|
||||
.query("projects")
|
||||
.withIndex("by_owner", (q) => q.eq("userId", userId)) // Note: Need to add index to schema too? Or just filter? Schema doesn't define indexes yet. Will rely on filter for now or filter in memory if small. Actually, will rely on simple filter or add index later.
|
||||
.filter((q) => q.eq(q.field("userId"), userId))
|
||||
.collect();
|
||||
},
|
||||
});
|
||||
|
||||
export const getDefaultProject = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) return null;
|
||||
return await ctx.db
|
||||
.query("projects")
|
||||
.filter((q) => q.and(q.eq(q.field("userId"), userId), q.eq(q.field("isDefault"), true)))
|
||||
.first();
|
||||
},
|
||||
});
|
||||
|
||||
export const createProject = mutation({
|
||||
args: { name: v.string(), isDefault: v.boolean() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) throw new Error("Unauthorized");
|
||||
|
||||
// If setting as default, unset other defaults? For now assume handled by UI or logic
|
||||
// Actually simplicity: just create.
|
||||
|
||||
return await ctx.db.insert("projects", {
|
||||
userId,
|
||||
name: args.name,
|
||||
isDefault: args.isDefault,
|
||||
dorkingConfig: { selectedSourceIds: [] },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const toggleDataSourceConfig = mutation({
|
||||
args: { projectId: v.id("projects"), sourceId: v.id("dataSources"), selected: v.boolean() },
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) throw new Error("Unauthorized");
|
||||
|
||||
const project = await ctx.db.get(args.projectId);
|
||||
if (!project || project.userId !== userId) throw new Error("Project not found or unauthorized");
|
||||
|
||||
let newSelectedIds = project.dorkingConfig.selectedSourceIds;
|
||||
if (args.selected) {
|
||||
if (!newSelectedIds.includes(args.sourceId)) {
|
||||
newSelectedIds.push(args.sourceId);
|
||||
}
|
||||
} else {
|
||||
newSelectedIds = newSelectedIds.filter((id) => id !== args.sourceId);
|
||||
}
|
||||
|
||||
await ctx.db.patch(args.projectId, {
|
||||
dorkingConfig: { selectedSourceIds: newSelectedIds },
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,9 +1,37 @@
|
||||
import { defineSchema, defineTable } from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
import { authTables } from "@convex-dev/auth/server";
|
||||
|
||||
const schema = defineSchema({
|
||||
...authTables,
|
||||
// Your other tables here
|
||||
projects: defineTable({
|
||||
userId: v.id("users"),
|
||||
name: v.string(),
|
||||
isDefault: v.boolean(),
|
||||
dorkingConfig: v.object({
|
||||
selectedSourceIds: v.array(v.id("dataSources")),
|
||||
}),
|
||||
}),
|
||||
dataSources: defineTable({
|
||||
projectId: v.id("projects"),
|
||||
type: v.literal("website"),
|
||||
url: v.string(),
|
||||
name: v.string(),
|
||||
analysisStatus: v.union(
|
||||
v.literal("pending"),
|
||||
v.literal("completed"),
|
||||
v.literal("failed")
|
||||
),
|
||||
analysisResults: v.optional(
|
||||
v.object({
|
||||
features: v.array(v.string()),
|
||||
painPoints: v.array(v.string()),
|
||||
keywords: v.array(v.string()),
|
||||
summary: v.string(),
|
||||
})
|
||||
),
|
||||
metadata: v.optional(v.any()),
|
||||
}),
|
||||
});
|
||||
|
||||
export default schema;
|
||||
|
||||
Reference in New Issue
Block a user