Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a0267da
feat(*): Admin flow initiate
Ayush8923 Mar 30, 2026
feea8c5
fix(*): added the note of after creating the key
Ayush8923 Mar 30, 2026
39604a2
fix(*): added the index.ts file for easy to import from the index
Ayush8923 Mar 30, 2026
fc7837f
fix(*): off the exhaustive deps
Ayush8923 Mar 30, 2026
e355d4a
fix(*): added the skeleton loader
Ayush8923 Mar 30, 2026
d4d4f1c
fix(*): some cleanups
Ayush8923 Mar 30, 2026
a4da445
fix(*): few clenaups
Ayush8923 Mar 31, 2026
713e9ad
feat(*): google integration flow
Ayush8923 Mar 31, 2026
06acfdc
feat(*): added the logout api endpoint
Ayush8923 Mar 31, 2026
b2ef783
fix(*): align the bottom action items tab
Ayush8923 Mar 31, 2026
3fbc5d3
fix(*): UI updates and users flow
Ayush8923 Apr 1, 2026
a236f2d
fix(*): small updates
Ayush8923 Apr 1, 2026
7e45d41
fix(*): some cases handling & UI updates
Ayush8923 Apr 2, 2026
e57c2e4
fix(*): some edge cases handled
Ayush8923 Apr 2, 2026
0e4d1dc
fix(*): authentication handle in the knowledge base
Ayush8923 Apr 2, 2026
8c4e3cc
fix(*): implement the edit projects
Ayush8923 Apr 2, 2026
bd1e94f
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 2, 2026
1b6598b
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 2, 2026
525f850
fix(*): remove the js comment
Ayush8923 Apr 2, 2026
2e3c91b
Merge branch 'main' of https://github.com/ProjectTech4DevAI/kaapi-fro…
Ayush8923 Apr 3, 2026
8a98eeb
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 3, 2026
32826e2
fix(*): some apikey intilization
Ayush8923 Apr 3, 2026
b6d59f8
fix(*): some of the UI updates
Ayush8923 Apr 3, 2026
3301f9d
fix(*): update the api response data
Ayush8923 Apr 4, 2026
9e14e58
fix(*): cleanups & handling refresh token
Ayush8923 Apr 5, 2026
be27625
fix(*): update the default setting route
Ayush8923 Apr 5, 2026
28513c8
Merge branch 'main' into feat/admin-flow
Ayush8923 Apr 6, 2026
9959073
Merge branch 'feat/admin-flow' into feat/google-integration
Ayush8923 Apr 6, 2026
1726212
Merge branch 'main' into feat/admin-flow
Ayush8923 Apr 7, 2026
4a0e744
Merge branch 'feat/admin-flow' into feat/google-integration
Ayush8923 Apr 7, 2026
275fcfc
fix(*): centralize the local storage key and clear the local storage …
Ayush8923 Apr 7, 2026
376e838
Merge branch 'main' of https://github.com/ProjectTech4DevAI/kaapi-fro…
Ayush8923 Apr 8, 2026
e223533
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 8, 2026
adb54ec
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 8, 2026
05bd0d3
fix(*): UI updates
Ayush8923 Apr 8, 2026
4c32ea0
Merge branch 'main' into feat/admin-flow
Ayush8923 Apr 8, 2026
e0c4783
Merge branch 'feat/admin-flow' into feat/google-integration
Ayush8923 Apr 8, 2026
06b672b
cleanups(*): stt and tts flow
Ayush8923 Apr 8, 2026
f985a1e
fix(*): few edge cases handles
Ayush8923 Apr 8, 2026
c206f3a
feat(*): User invitation flow
Ayush8923 Apr 8, 2026
fa0490f
fix(*): move invite page inside the auth group
Ayush8923 Apr 8, 2026
a20bb4d
fix(*): few updates on invite page
Ayush8923 Apr 9, 2026
16f139e
Feat: Google Integration (#102)
Ayush8923 Apr 9, 2026
44cd484
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevA…
Ayush8923 Apr 9, 2026
bed2d71
fix(*): update the invite page
Ayush8923 Apr 9, 2026
250769d
Merge branch 'main' of https://github.com/ProjectTech4DevAI/kaapi-fro…
Ayush8923 Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions app/(auth)/invite/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
"use client";

import { useEffect, useState, Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { useAuth } from "@/app/lib/context/AuthContext";
import { InviteVerifyResponse } from "@/app/lib/types/auth";
import {
CheckCircleIcon,
WarningIcon,
SpinnerIcon,
} from "@/app/components/icons";
import { Button } from "@/app/components";

type Status = "verifying" | "success" | "error";

function InviteContent() {
const searchParams = useSearchParams();
const router = useRouter();
const { loginWithToken } = useAuth();
const [status, setStatus] = useState<Status>("verifying");
const [error, setError] = useState("");
const [progress, setProgress] = useState(0);

useEffect(() => {
const token = searchParams.get("token");

if (!token) {
setStatus("error");
setError("Invalid invitation link. No token found.");
return;
}

let cancelled = false;

(async () => {
try {
const res = await fetch(
`/api/auth/invite?token=${encodeURIComponent(token)}`,
{ credentials: "include" },
);

const data: InviteVerifyResponse = await res.json();

if (cancelled) return;

if (!res.ok || !data.success || !data.data) {
setStatus("error");
setError(data.error || "Invitation link is invalid or has expired.");
return;
}

loginWithToken(data.data.access_token, data.data.user);
setStatus("success");
} catch {
if (!cancelled) {
setStatus("error");
setError("Failed to verify invitation. Please try again.");
}
}
})();

return () => {
cancelled = true;
};
}, [searchParams, loginWithToken]);

useEffect(() => {
if (status !== "success") return;

const duration = 2000;
const interval = 30;
let elapsed = 0;

const timer = setInterval(() => {
elapsed += interval;
const pct = Math.min((elapsed / duration) * 100, 100);
setProgress(pct);

if (elapsed >= duration) {
clearInterval(timer);
router.push("/evaluations");
}
}, interval);

return () => clearInterval(timer);
}, [status, router]);

return (
<div className="min-h-screen bg-bg-secondary flex flex-col items-center justify-center p-4 relative overflow-hidden">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-1/2 -right-1/4 w-[600px] h-[600px] rounded-full bg-linear-to-br from-blue-50 to-purple-50 opacity-60 blur-3xl" />
<div className="absolute -bottom-1/2 -left-1/4 w-[500px] h-[500px] rounded-full bg-linear-to-tr from-green-50 to-blue-50 opacity-40 blur-3xl" />
</div>

<div className="w-full max-w-sm relative z-10">
<div className="text-center mb-8">
<h2 className="text-lg font-semibold text-text-primary tracking-tight">
Kaapi Konsole
</h2>
<p className="text-xs text-text-secondary mt-0.5">by Tech4Dev</p>
</div>

<div
className={`bg-white rounded-2xl border shadow-sm overflow-hidden transition-all duration-500 ${
status === "error"
? "border-red-200"
: status === "success"
? "border-green-200"
: "border-border"
}`}
>
<div
className="h-1 transition-all duration-700"
style={{
background:
status === "error"
? "linear-gradient(90deg, #fca5a5, #ef4444)"
: status === "success"
? "linear-gradient(90deg, #86efac, #22c55e)"
: "linear-gradient(90deg, #dbeafe, #c7d2fe, #ddd6fe)",
}}
/>

<div className="px-8 py-10">
<div className="flex justify-center mb-5">
<div
className={`w-16 h-16 rounded-full flex items-center justify-center transition-all duration-500 ${
status === "verifying"
? "bg-neutral-50 border border-border"
: status === "success"
? "bg-green-50 border border-green-200"
: "bg-red-50 border border-red-200"
}`}
>
{status === "verifying" && (
<SpinnerIcon className="w-7 h-7 text-text-secondary animate-spin" />
)}
{status === "success" && (
<CheckCircleIcon className="w-8 h-8 text-green-600" />
)}
{status === "error" && (
<WarningIcon className="w-8 h-8 text-red-500" />
)}
</div>
</div>

{/* Title */}
<h1 className="text-center text-xl font-semibold text-text-primary mb-2">
{status === "verifying" && "Verifying invitation"}
{status === "success" && "Welcome aboard!"}
{status === "error" && "Something went wrong"}
</h1>

<p className="text-sm text-text-secondary leading-relaxed">
{status === "verifying" &&
"Please wait while we verify your invitation and set up your account."}
{status === "success" &&
"Your account has been activated. Redirecting you to the dashboard..."}
{status === "error" && error}
</p>

{status === "success" && (
<div className="mt-6 flex justify-center">
<div className="h-1 w-32 rounded-full bg-neutral-100 overflow-hidden">
<div
className="h-full bg-green-500 rounded-full transition-[width] duration-75 ease-linear"
style={{ width: `${progress}%` }}
/>
</div>
</div>
)}

{status === "verifying" && (
<div className="mt-6 flex justify-center gap-1.5">
{[0, 1, 2].map((i) => (
<div
key={i}
className="w-1.5 h-1.5 rounded-full bg-text-secondary animate-pulse"
style={{ animationDelay: `${i * 200}ms` }}
/>
))}
</div>
)}

{status === "error" && (
<div className="mt-8 space-y-3">
<Button fullWidth onClick={() => router.push("/evaluations")}>
Go to Dashboard
</Button>
<button
onClick={() => window.location.reload()}
className="w-full text-center text-xs text-text-secondary hover:text-text-primary transition-colors cursor-pointer"
>
Try again
</button>
</div>
)}
</div>
</div>

{status === "error" && (
<p className="text-center text-xs text-text-secondary mt-5 leading-relaxed">
If this keeps happening, please contact your organization
administrator for a new invitation link.
</p>
)}
</div>
</div>
);
}

export default function InvitePage() {
return (
<Suspense
fallback={
<div className="min-h-screen bg-bg-secondary flex items-center justify-center">
<SpinnerIcon className="w-8 h-8 text-text-secondary animate-spin" />
</div>
}
>
<InviteContent />
</Suspense>
);
}
35 changes: 35 additions & 0 deletions app/api/auth/invite/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { NextRequest, NextResponse } from "next/server";
import { apiClient } from "@/app/lib/apiClient";

export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const token = searchParams.get("token");

if (!token) {
return NextResponse.json(
{ success: false, error: "Missing invitation token" },
{ status: 400 },
);
}

const { status, data, headers } = await apiClient(
request,
`/api/v1/auth/invite/verify?token=${encodeURIComponent(token)}`,
);

const res = NextResponse.json(data, { status });

const setCookies = headers.getSetCookie?.() ?? [];
for (const cookie of setCookies) {
res.headers.append("Set-Cookie", cookie);
}

return res;
Comment on lines +21 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether auth route handlers set explicit no-store cache headers.
fd -p 'route.ts' app/api/auth | while read -r f; do
  echo "== $f =="
  rg -n "Cache-Control|Pragma|NextResponse\\.json|Set-Cookie" "$f"
done

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 740


🏁 Script executed:

cat -n app/api/auth/invite/route.ts

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 1247


🏁 Script executed:

# Check if there's middleware that might handle cache headers globally
find . -name "middleware.ts" -o -name "middleware.js" | head -5

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Search for any global cache-control patterns in the codebase
rg -l "Cache-Control|no-store" --type ts --type tsx | head -10

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 103


🏁 Script executed:

# Check other API routes to see if any set headers via NextResponse.json options
rg -n "NextResponse\.json.*headers" --type ts

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Look for NextResponse patterns with options to understand the API
rg -B1 -A3 "NextResponse\.json.*\{" app/api --type ts | head -50

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 2853


🏁 Script executed:

# Check if there's a Next.js config or any cache-related setup
cat next.config.js 2>/dev/null || cat next.config.ts 2>/dev/null || echo "No next.config found"

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 317


🏁 Script executed:

# Check the google and logout routes to see their full pattern
cat -n app/api/auth/google/route.ts

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 1419


🏁 Script executed:

# Search for any response.headers patterns to understand how headers are actually set
rg "response\.headers|res\.headers" app/api -A2 -B2 --type ts | head -80

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 1253


🏁 Script executed:

# Check if NextResponse constructor accepts headers option by looking for similar patterns
rg "NextResponse\.(json|text|redirect)" --type ts -A5 | grep -E "NextResponse|headers" | head -40

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 3002


🏁 Script executed:

# Look for NextResponse type definitions or usage with headers
rg "NextResponse" node_modules --type ts -A5 2>/dev/null | head -100

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Check if there's a tsconfig that might have type definitions
cat tsconfig.json 2>/dev/null | head -30

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 690


🏁 Script executed:

# Search for any example in the codebase where response object headers are set before construction
rg "headers\s*=" app/api --type ts -B3 -A3 | head -80

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Look at Next.js version to understand NextResponse API
cat package.json | grep -A5 -B5 "next"

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 972


🏁 Script executed:

# Check Next.js 16 documentation or search for ResponseInit usage
rg "ResponseInit|init\?" app/api --type ts -B2 -A2 | head -60

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Look for any headers being set in constructor anywhere in the codebase
rg "new NextResponse\|NextResponse\.(json|text)" --type ts -A8 | grep -E "NextResponse|headers" | head -50

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


🏁 Script executed:

# Verify the actual Next.js API: check if response headers can be set in constructor
python3 << 'EOF'
import json

# Next.js NextResponse API for version 16.0.7 analysis
# Based on Next.js documentation, ResponseInit includes:
# { status?: number; statusText?: string; headers?: HeadersInit }

# This means headers can be passed in the options object
# The proposed patch should work syntactically

print("NextResponse.json() signature in Next.js 16.0.7:")
print("- Accepts: (body, init?: ResponseInit)")
print("- ResponseInit includes: { status, headers, statusText }")
print("\nProposed patch syntax appears valid.")
print("\nThe concern raised in review is legitimate:")
print("- GET endpoint returns sensitive auth data")
print("- Returns Set-Cookie headers")
print("- Should be explicitly non-cacheable")
EOF

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 412


Add explicit no-cache headers to invite verification response.

This GET endpoint returns sensitive auth data and forwards session cookies. Responses should be marked non-cacheable to prevent browsers or proxies from caching the auth token/cookies.

🔧 Proposed patch
-    const res = NextResponse.json(data, { status });
+    const res = NextResponse.json(data, {
+      status,
+      headers: {
+        "Cache-Control": "no-store, private, max-age=0",
+        Pragma: "no-cache",
+      },
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const res = NextResponse.json(data, { status });
const setCookies = headers.getSetCookie?.() ?? [];
for (const cookie of setCookies) {
res.headers.append("Set-Cookie", cookie);
}
return res;
const res = NextResponse.json(data, {
status,
headers: {
"Cache-Control": "no-store, private, max-age=0",
Pragma: "no-cache",
},
});
const setCookies = headers.getSetCookie?.() ?? [];
for (const cookie of setCookies) {
res.headers.append("Set-Cookie", cookie);
}
return res;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/auth/invite/route.ts` around lines 21 - 28, The response returned
from NextResponse.json (const res) must include explicit no-cache headers to
prevent caching of sensitive auth data and forwarded cookies; after building res
and before returning it (near NextResponse.json, headers.getSetCookie, and
res.headers.append usage), set Cache-Control to "no-store, no-cache,
must-revalidate, max-age=0" and add complementary headers such as "Pragma:
no-cache" and "Expires: 0" on res.headers (using res.headers.set or equivalent)
so the invite verification response is non-cacheable.

} catch {
return NextResponse.json(
{ success: false, error: "Failed to connect to backend" },
{ status: 500 },
);
}
}
4 changes: 2 additions & 2 deletions app/components/auth/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface LoginModalProps {

export default function LoginModal({ open, onClose }: LoginModalProps) {
const router = useRouter();
const { loginWithGoogle } = useAuth();
const { loginWithToken } = useAuth();
const toast = useToast();
const [isLoggingIn, setIsLoggingIn] = useState(false);

Expand All @@ -38,7 +38,7 @@ export default function LoginModal({ open, onClose }: LoginModalProps) {
});

const { access_token, user, google_profile } = res.data;
loginWithGoogle(access_token, user, google_profile);
loginWithToken(access_token, user, google_profile);
onClose();
} catch (err) {
toast.error(
Expand Down
9 changes: 9 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,12 @@ a {
border: 3px solid #e5e5e5;
border-top-color: #171717;
}

@keyframes progress {
from {
width: 0%;
}
to {
width: 100%;
}
}
4 changes: 2 additions & 2 deletions app/lib/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
);
const setKeys = useCallback((keys: APIKey[]) => persist(keys), [persist]);

const loginWithGoogle = useCallback(
const loginWithToken = useCallback(
(accessToken: string, user?: User, googleProfile?: GoogleProfile) => {
const newSession: Session = {
accessToken,
Expand Down Expand Up @@ -160,7 +160,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
addKey,
removeKey,
setKeys,
loginWithGoogle,
loginWithToken,
logout,
}}
>
Expand Down
12 changes: 11 additions & 1 deletion app/lib/types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ export interface AuthTokenResponse {
error?: string;
}

export interface InviteVerifyResponse {
success: boolean;
data?: {
access_token: string;
token_type: string;
user: User;
};
error?: string;
}

export interface Session {
accessToken: string;
user: User | null;
Expand All @@ -61,7 +71,7 @@ export interface AuthContextValue {
addKey: (key: APIKey) => void;
removeKey: (id: string) => void;
setKeys: (keys: APIKey[]) => void;
loginWithGoogle: (
loginWithToken: (
accessToken: string,
user?: User,
googleProfile?: GoogleProfile,
Expand Down
Loading