Skip to content

Commit bd1e94f

Browse files
committed
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevAI/kaapi-frontend into feat/google-integration
2 parents 0e4d1dc + 8c4e3cc commit bd1e94f

File tree

13 files changed

+458
-52
lines changed

13 files changed

+458
-52
lines changed

app/(main)/datasets/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import Sidebar from "@/app/components/Sidebar";
1616
import PageHeader from "@/app/components/PageHeader";
1717
import { useToast } from "@/app/components/Toast";
1818

19-
// Backend response interface
2019
export interface Dataset {
2120
dataset_id: number;
2221
dataset_name: string;

app/(main)/knowledge-base/page.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ export default function KnowledgeBasePage() {
773773
fetchCollections();
774774
fetchDocuments();
775775
}
776-
}, [isAuthenticated]);
776+
}, [apiKey]);
777777

778778
// Keep apiKeyRef in sync so polling always has the current key
779779
useEffect(() => {
@@ -1135,11 +1135,9 @@ export default function KnowledgeBasePage() {
11351135
)}
11361136
</div>
11371137

1138-
{/* Document List */}
11391138
{selectedCollection.documents &&
11401139
selectedCollection.documents.length > 0 ? (
11411140
<div>
1142-
{/* Header Row */}
11431141
<div className="flex items-center justify-between pb-2 mb-2 border-b border-border">
11441142
<div className="flex-1">
11451143
<p className="text-[10px] font-semibold uppercase text-text-secondary">

app/(main)/settings/onboarding/page.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ export default function OnboardingPage() {
134134
[activeKey],
135135
);
136136

137+
const refreshProjects = useCallback(async () => {
138+
if (!selectedOrg) return;
139+
try {
140+
const result = await apiFetch<ProjectListResponse>(
141+
`/api/organization/${selectedOrg.id}/projects`,
142+
activeKey?.key ?? "",
143+
);
144+
if (result.success && result.data) {
145+
setProjects(result.data);
146+
}
147+
} catch {
148+
// keep current list
149+
}
150+
}, [selectedOrg, activeKey]);
151+
137152
const handleSuccess = (data: OnboardResponseData) => {
138153
setOnboardData(data);
139154
setView("success");
@@ -191,6 +206,7 @@ export default function OnboardingPage() {
191206
isLoading={isLoadingProjects}
192207
onBack={handleBackToOrgs}
193208
onSelectProject={handleSelectProject}
209+
onProjectAdded={refreshProjects}
194210
/>
195211
)}
196212

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { apiClient } from "@/app/lib/apiClient";
3+
4+
export async function PATCH(
5+
request: NextRequest,
6+
{ params }: { params: Promise<{ projectId: string }> },
7+
) {
8+
try {
9+
const { projectId } = await params;
10+
const body = await request.json();
11+
const { status, data } = await apiClient(
12+
request,
13+
`/api/v1/projects/${projectId}`,
14+
{
15+
method: "PATCH",
16+
body: JSON.stringify(body),
17+
},
18+
);
19+
return NextResponse.json(data, { status });
20+
} catch {
21+
return NextResponse.json(
22+
{ success: false, error: "Failed to connect to backend" },
23+
{ status: 500 },
24+
);
25+
}
26+
}

app/api/projects/route.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { apiClient } from "@/app/lib/apiClient";
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
const body = await request.json();
7+
const { status, data } = await apiClient(request, "/api/v1/projects/", {
8+
method: "POST",
9+
body: JSON.stringify(body),
10+
});
11+
return NextResponse.json(data, { status });
12+
} catch {
13+
return NextResponse.json(
14+
{ success: false, error: "Failed to connect to backend" },
15+
{ status: 500 },
16+
);
17+
}
18+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Modal from "@/app/components/Modal";
5+
import { Button, Field } from "@/app/components";
6+
import {
7+
AddProjectModalProps,
8+
ProjectResponse,
9+
} from "@/app/lib/types/onboarding";
10+
import { apiFetch } from "@/app/lib/apiClient";
11+
import { useToast } from "@/app/components/Toast";
12+
13+
export default function AddProjectModal({
14+
open,
15+
onClose,
16+
organizationId,
17+
apiKey,
18+
onProjectAdded,
19+
}: AddProjectModalProps) {
20+
const toast = useToast();
21+
const [name, setName] = useState("");
22+
const [description, setDescription] = useState("");
23+
const [isActive, setIsActive] = useState(true);
24+
const [isSubmitting, setIsSubmitting] = useState(false);
25+
const [nameError, setNameError] = useState("");
26+
27+
const resetForm = () => {
28+
setName("");
29+
setDescription("");
30+
setIsActive(true);
31+
setNameError("");
32+
};
33+
34+
const handleClose = () => {
35+
resetForm();
36+
onClose();
37+
};
38+
39+
const handleSubmit = async () => {
40+
if (!name.trim()) {
41+
setNameError("Project name is required");
42+
return;
43+
}
44+
45+
setNameError("");
46+
setIsSubmitting(true);
47+
48+
try {
49+
await apiFetch<ProjectResponse>("/api/projects", apiKey, {
50+
method: "POST",
51+
body: JSON.stringify({
52+
name: name.trim(),
53+
description: description.trim(),
54+
is_active: isActive,
55+
organization_id: organizationId,
56+
}),
57+
});
58+
59+
toast.success(`Project "${name.trim()}" created`);
60+
resetForm();
61+
onProjectAdded();
62+
onClose();
63+
} catch (err) {
64+
toast.error(
65+
err instanceof Error ? err.message : "Failed to create project",
66+
);
67+
} finally {
68+
setIsSubmitting(false);
69+
}
70+
};
71+
72+
return (
73+
<Modal
74+
open={open}
75+
onClose={handleClose}
76+
title="Create Project"
77+
maxWidth="max-w-md"
78+
>
79+
<div className="px-6 pb-6 space-y-4">
80+
<Field
81+
label="Name *"
82+
value={name}
83+
onChange={(val) => {
84+
setName(val);
85+
if (nameError) setNameError("");
86+
}}
87+
placeholder="Enter project name"
88+
error={nameError}
89+
/>
90+
91+
<Field
92+
label="Description"
93+
value={description}
94+
onChange={setDescription}
95+
placeholder="Enter project description (optional)"
96+
/>
97+
98+
<div>
99+
<label className="flex items-center gap-2.5 cursor-pointer">
100+
<input
101+
type="checkbox"
102+
checked={isActive}
103+
onChange={(e) => setIsActive(e.target.checked)}
104+
className="w-4 h-4 rounded"
105+
/>
106+
<span className="text-sm text-text-primary">Active</span>
107+
</label>
108+
<p className="text-xs text-text-secondary mt-1 ml-6.5">
109+
Inactive projects won&apos;t be available for use.
110+
</p>
111+
</div>
112+
113+
<div className="flex items-center justify-end gap-2 pt-4 border-t border-border">
114+
<Button variant="outline" size="md" onClick={handleClose}>
115+
Cancel
116+
</Button>
117+
<Button
118+
onClick={handleSubmit}
119+
disabled={isSubmitting || !name.trim()}
120+
size="md"
121+
>
122+
{isSubmitting ? "Creating..." : "Create Project"}
123+
</Button>
124+
</div>
125+
</div>
126+
</Modal>
127+
);
128+
}

app/components/settings/onboarding/AddUserModal.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,13 @@ import { useState } from "react";
44
import Modal from "@/app/components/Modal";
55
import { Button, Field } from "@/app/components";
66
import { isValidEmail } from "@/app/lib/utils";
7-
import { UserProjectListResponse } from "@/app/lib/types/onboarding";
7+
import {
8+
AddUserModalProps,
9+
UserProjectListResponse,
10+
} from "@/app/lib/types/onboarding";
811
import { apiFetch } from "@/app/lib/apiClient";
912
import { useToast } from "@/app/components/Toast";
1013

11-
interface AddUserModalProps {
12-
open: boolean;
13-
onClose: () => void;
14-
organizationId: number;
15-
projectId: number;
16-
apiKey: string;
17-
onUsersAdded: () => void;
18-
}
19-
2014
export default function AddUserModal({
2115
open,
2216
onClose,
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import Modal from "@/app/components/Modal";
5+
import { Button, Field } from "@/app/components";
6+
import {
7+
EditProjectModalProps,
8+
ProjectResponse,
9+
} from "@/app/lib/types/onboarding";
10+
import { apiFetch } from "@/app/lib/apiClient";
11+
import { useToast } from "@/app/components/Toast";
12+
13+
export default function EditProjectModal({
14+
open,
15+
onClose,
16+
project,
17+
apiKey,
18+
onProjectUpdated,
19+
}: EditProjectModalProps) {
20+
const toast = useToast();
21+
const [name, setName] = useState(project.name);
22+
const [description, setDescription] = useState(project.description);
23+
const [isActive, setIsActive] = useState(project.is_active);
24+
const [isSubmitting, setIsSubmitting] = useState(false);
25+
const [nameError, setNameError] = useState("");
26+
27+
useEffect(() => {
28+
setName(project.name);
29+
setDescription(project.description);
30+
setIsActive(project.is_active);
31+
setNameError("");
32+
}, [project]);
33+
34+
const handleClose = () => {
35+
setName(project.name);
36+
setDescription(project.description);
37+
setIsActive(project.is_active);
38+
setNameError("");
39+
onClose();
40+
};
41+
42+
const handleSubmit = async () => {
43+
if (!name.trim()) {
44+
setNameError("Project name is required");
45+
return;
46+
}
47+
48+
setNameError("");
49+
setIsSubmitting(true);
50+
51+
try {
52+
await apiFetch<ProjectResponse>(`/api/projects/${project.id}`, apiKey, {
53+
method: "PATCH",
54+
body: JSON.stringify({
55+
name: name.trim(),
56+
description: description.trim(),
57+
is_active: isActive,
58+
}),
59+
});
60+
61+
toast.success("Project updated");
62+
onProjectUpdated();
63+
onClose();
64+
} catch (err) {
65+
toast.error(
66+
err instanceof Error ? err.message : "Failed to update project",
67+
);
68+
} finally {
69+
setIsSubmitting(false);
70+
}
71+
};
72+
73+
return (
74+
<Modal
75+
open={open}
76+
onClose={handleClose}
77+
title="Edit Project"
78+
maxWidth="max-w-md"
79+
>
80+
<div className="px-6 pb-6 space-y-4">
81+
<Field
82+
label="Name *"
83+
value={name}
84+
onChange={(val) => {
85+
setName(val);
86+
if (nameError) setNameError("");
87+
}}
88+
placeholder="Enter project name"
89+
error={nameError}
90+
/>
91+
92+
<Field
93+
label="Description"
94+
value={description}
95+
onChange={setDescription}
96+
placeholder="Enter project description (optional)"
97+
/>
98+
99+
<div>
100+
<label className="flex items-center gap-2.5 cursor-pointer">
101+
<input
102+
type="checkbox"
103+
checked={isActive}
104+
onChange={(e) => setIsActive(e.target.checked)}
105+
className="w-4 h-4 rounded"
106+
/>
107+
<span className="text-sm text-text-primary">Active</span>
108+
</label>
109+
<p className="text-xs text-text-secondary mt-1 ml-6.5">
110+
Inactive projects won&apos;t be available for use.
111+
</p>
112+
</div>
113+
114+
<div className="flex items-center justify-end gap-2 pt-4 border-t border-border">
115+
<Button variant="outline" size="md" onClick={handleClose}>
116+
Cancel
117+
</Button>
118+
<Button
119+
onClick={handleSubmit}
120+
disabled={isSubmitting || !name.trim()}
121+
size="md"
122+
>
123+
{isSubmitting ? "Saving..." : "Save Changes"}
124+
</Button>
125+
</div>
126+
</div>
127+
</Modal>
128+
);
129+
}

0 commit comments

Comments
 (0)