Skip to content

Conversation

@PinJinx
Copy link
Contributor

@PinJinx PinJinx commented Aug 6, 2025

Closes #115

📝 Description

In this PR i have added a basic Authentication system for the frontend with supabases built in email authentication system.

🔧 Changes Made

  • I have added a new Signup page that prompts user to signup using email password and username.
  • Made changes to the Login Page to use the same system. Also edited the App.tsx to Auto login the user once they are logged in.

📷 Screenshots or Visual Changes (if applicable)

Login Screen

image

SignUp Screen

image

After SignUp Screen

image

🤝 Collaboration

nil

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • Full Supabase-powered auth: real Login, Sign Up, Sign Out with session handling and global toasts.
    • Password recovery: request reset email, reset password flow, and dedicated pages with confirmations and validations.
    • Protected routing with auth gating and smooth animated page transitions.
  • Chores

    • Added Supabase client and dependency to frontend.
  • Documentation

    • Added frontend env example for Supabase URL, anon key, and base URL.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Walkthrough

Adds Supabase authentication to the frontend: environment example and client, replaces mock login with supabase auth flows, adds signup/forgot/reset password pages, updates App routing and session handling, adjusts LandingPage navigation, and a minor editor config change.

Changes

Cohort / File(s) Summary
Editor config
\.vimrc
Added syntax-highlighting=True.
Env configuration
frontend/.env example
Added VITE_SUPABASE_URL, VITE_SUPABASE_KEY, VITE_BASE_URL placeholders.
Dependencies
frontend/package.json
Added dependency @supabase/supabase-js ^2.53.0.
Supabase client
frontend/src/lib/supabaseClient.ts
New module exporting supabase, reads Vite env vars and throws if missing.
App routing & auth
frontend/src/App.tsx
Replaced localStorage mock with supabase.session checks, onAuthStateChange listener, async signOut, added /signup, /forgot-password, /reset-password routes, ProtectedLayout usage.
Login page
frontend/src/components/pages/LoginPage.tsx
Switched to supabase.auth.signInWithPassword, extracts form data via name attributes, shows toasts, navigates on success; forgot-password navigates to route.
Sign up page
frontend/src/components/pages/SignUpPage.tsx
New page: client-side password validation, supabase.auth.signUp, confirmation view, animations and toasts.
Forgot password (request)
frontend/src/components/pages/ForgotPasswrdPage.tsx
New page: supabase.auth.resetPasswordForEmail with redirectTo, confirmation state and toasts.
Reset password
frontend/src/components/pages/ResetPasswordPage.tsx
New page: parses tokens from URL hash, sets session via supabase.auth.setSession, validates and updates password via supabase.auth.updateUser.
Profile / Landing adjustments
frontend/src/components/pages/ProfilePage.tsx, frontend/src/components/landing/LandingPage.tsx
Minor import/format change in ProfilePage; LandingPage now uses react-router useNavigate and removed setActivePage prop.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as App.tsx
  participant Client as supabase (client)
  participant Pages as Router/Pages

  rect #eef7ff Auth Init
    User->>App: Load app
    App->>Client: auth.getSession()
    Client-->>App: session|null
    App->>App: set isAuthenticated
    App->>Client: onAuthStateChange(listener)
  end

  rect #f0ffef Login Flow
    User->>Pages: Submit Login form
    Pages->>Client: auth.signInWithPassword(email,password)
    alt success
      Client-->>Pages: session
      Pages->>App: onLogin()
      Pages->>User: Navigate to "/"
    else error
      Client-->>Pages: error
      Pages->>User: Show error toast
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Integrate Supabase email/password auth for login and signup (#115)
Maintain user sessions using Supabase client-side handling (#115)
Redirect users appropriately based on auth status (#115)
Add basic error handling for auth flows (#115)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add syntax-highlighting=True (.vimrc) Editor preference unrelated to implementing Supabase authentication.

Possibly related PRs

Suggested reviewers

  • smokeyScraper

Poem

I hopped through routes with keys in paw,
New doors now open — no more mocky flaw.
Supa-bunny signs in with a cheer,
Resets a password, then snacks on a pear.
Sessions snug like burrowed hay — off I hop to play! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (1)
.vimrc (1)

1-3: .vimrc shouldn’t live in the repo ‒ and the directive is invalid anyway

  1. Committing personal editor configuration clutters the project root and forces unrelated settings on every contributor. If shared editor settings are truly needed, place them under ./.editorconfig, ./.vscode/, or a dedicated tools/ide/ folder—otherwise drop the file from the PR.
  2. syntax-highlighting=True is not a valid Vim command; the canonical directive is simply syntax on (or syntax enable).

Recommended fix:

- syntax-highlighting=True
+ " Remove this file from the repository, or, if deliberate:
+ syntax on
🧹 Nitpick comments (7)
frontend/.env example (1)

1-3: Improve placeholder format for environment variables.

The environment variable placeholders contain spaces which could cause configuration issues if copied directly. Consider using underscores or hyphens instead.

-VITE_SUPABASE_URL=YOUR SUPABASE URL
+VITE_SUPABASE_URL=YOUR_SUPABASE_URL

-VITE_SUPABASE_KEY=YOUR SUPABASE ANON KEY
+VITE_SUPABASE_KEY=YOUR_SUPABASE_ANON_KEY
frontend/src/lib/supabaseClient.tsx (1)

1-9: Consider changing file extension from .tsx to .ts.

This file contains no JSX elements, so the .ts extension would be more appropriate than .tsx.

frontend/src/App.tsx (1)

84-91: Fix formatting and spacing consistency.

The signup route formatting is inconsistent with the login route above it.

           <Route
             path="/signup"
-            element= {isAuthenticated ? (
-                <Navigate to="/" replace />
-              ) : (
-                <SignUpPage/>
-              )}
+            element={
+              isAuthenticated ? (
+                <Navigate to="/" replace />
+              ) : (
+                <SignUpPage />
+              )
+            }
           />
frontend/src/components/pages/SignUpPage.tsx (4)

23-25: Remove unused interface.

The LoginPageProps interface is not used in this SignUpPage component and appears to be leftover from copying the LoginPage component.

-interface LoginPageProps {
-  onLogin: () => void;
-}

107-112: Use appropriate icon for username field.

The username field is using the Mail icon, which is semantically incorrect and confusing for users.

+import {
+  Settings,
+  Mail,
+  Lock,
+  LogIn,
+  User
+} from 'lucide-react';

  <InputField
-   icon={Mail}
+   icon={User}
    name="name"
    placeholder="Username"
    required
  />

191-197: Remove unnecessary state update before navigation.

Setting mailPage to false before navigating away is unnecessary since the component will unmount.

  <button
    type="button"
-   onClick={() => { setMailPage(false); navigate('/login') }}
+   onClick={() => navigate('/login')}
    className="mt-4 px-6 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition-colors"
  >
    Back to Sign In
  </button>

105-134: Consider adding client-side form validation for better UX.

Adding real-time validation feedback would improve user experience by catching errors before form submission.

Consider implementing client-side validation with visual feedback:

const [errors, setErrors] = useState<{[key: string]: string}>({});

const validateField = (name: string, value: string) => {
  const newErrors = { ...errors };
  
  switch (name) {
    case 'email':
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (!emailRegex.test(value)) {
        newErrors.email = 'Please enter a valid email address';
      } else {
        delete newErrors.email;
      }
      break;
    case 'password':
      if (value.length < 6) {
        newErrors.password = 'Password must be at least 6 characters';
      } else {
        delete newErrors.password;
      }
      break;
  }
  
  setErrors(newErrors);
};

Then add onChange handlers to InputField components and display error messages.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1159d7 and f55700b.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .vimrc (1 hunks)
  • frontend/.env example (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/App.tsx (2 hunks)
  • frontend/src/components/pages/LoginPage.tsx (3 hunks)
  • frontend/src/components/pages/SignUpPage.tsx (1 hunks)
  • frontend/src/lib/supabaseClient.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/components/pages/LoginPage.tsx (1)
frontend/src/lib/supabaseClient.tsx (1)
  • supabase (8-8)
🔇 Additional comments (7)
frontend/package.json (1)

13-13: Supabase dependency version is current and secure

As of August 6, 2025, @supabase/supabase-js@^2.53.0 is the latest release and has no publicly reported security vulnerabilities. Continue to monitor the Supabase GitHub repo and major CVE databases for future updates.

• File frontend/package.json, line 13: "@supabase/supabase-js": "^2.53.0",

frontend/src/components/pages/LoginPage.tsx (3)

56-63: LGTM on form data extraction and authentication call.

The FormData approach for extracting form values and the Supabase authentication call are implemented correctly.


93-93: LGTM on form field naming.

Adding name attributes to form fields enables proper FormData extraction.


100-100: LGTM on form field naming.

Adding name attributes to form fields enables proper FormData extraction.

frontend/src/components/pages/SignUpPage.tsx (3)

29-40: LGTM!

The AuthLayout component provides a clean, reusable wrapper with proper responsive design and smooth animations.


42-52: LGTM!

The InputField component is well-designed with proper TypeScript typing, icon integration, and consistent styling. Good use of prop spreading for flexibility.


55-58: LGTM!

Clean component setup with appropriate state management and hook usage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (6)
frontend/src/components/pages/ResetPasswrdPage.tsx (4)

83-85: Use strict equality and fix grammar

Minor correctness and UX polish.

-    if (password != repassword) {
-      toast.error("Passwords doesn't match. Try Again");
+    if (password !== repassword) {
+      toast.error("Passwords do not match. Try again.");
       return;
     }

88-95: Optional: confirm success to the user before redirect

A quick feedback toast improves UX.

-    if (error) {
+    if (error) {
       toast.error(error.message || "An unknown error occurred!");
       return;
     }
-    navigate("/");
+    toast.success("Password updated successfully.");
+    navigate("/", { replace: true });

111-118: Client-side validation hints for password fields

Add autoComplete="new-password" and a minimal length consistent with Supabase (default 6). This improves UX and browser behavior.

             <InputField
               icon={Key}
               type="password"
               name="password-1"
               className="mb-7"
               placeholder="New Password"
+              autoComplete="new-password"
+              minLength={6}
               required
             />
             <InputField
               icon={Key}
               type="password"
               name="password-2"
               className="mb-7"
               placeholder="Reenter Password"
+              autoComplete="new-password"
+              minLength={6}
               required
             />

Also applies to: 119-126


21-33: Deduplicate local Auth UI (DRY)

AuthLayout and InputField are likely repeated across auth pages. Centralize them under a shared components/auth/ module to reduce duplication and ensure consistent fixes/styles in one place.

I can draft a shared AuthLayout and InputField with full type safety and className merging. Want me to open a follow-up PR?

Also applies to: 34-44

frontend/src/components/pages/ForgotPasswrdPage.tsx (2)

36-46: Caller-supplied className is lost

<InputField> spreads ...props before hard-coding className, so any className passed by the caller (e.g. mb-7) is overridden. Merge the classes instead:

- <input
-   {...props}
-   className="block w-full pl-10 pr-3 py-2 border ... "
- />
+ const { className, ...rest } = props;
+ <input
+   {...rest}
+   className={`block w-full pl-10 pr-3 py-2 border ... ${className ?? ''}`}
+ />

49-49: Typo in component/file name

ForgotPasswrdPage is missing an “o”. Standardising to ForgotPasswordPage improves discoverability and avoids future import mistakes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f55700b and 6e2cdb0.

📒 Files selected for processing (6)
  • frontend/src/App.tsx (2 hunks)
  • frontend/src/components/pages/ForgotPasswrdPage.tsx (1 hunks)
  • frontend/src/components/pages/LoginPage.tsx (4 hunks)
  • frontend/src/components/pages/ResetPasswrdPage.tsx (1 hunks)
  • frontend/src/components/pages/SignUpPage.tsx (1 hunks)
  • frontend/src/lib/supabaseClient.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/lib/supabaseClient.tsx
  • frontend/src/components/pages/SignUpPage.tsx
  • frontend/src/App.tsx
  • frontend/src/components/pages/LoginPage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/components/pages/ForgotPasswrdPage.tsx (2)
frontend/src/lib/supabaseClient.tsx (1)
  • supabase (12-12)
backend/app/core/config/settings.py (1)
  • Settings (8-51)

@smokeyScraper
Copy link
Contributor

Hey @PinJinx ,
Thanks a lot for this. Could you please resolve the issues pointed out by coderabbit if they seem relevant?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
frontend/src/components/pages/ResetPasswordPage.tsx (4)

67-90: Validate type=recovery before setting session

Supabase recovery links include type=recovery. Guarding on this reduces accidental entry to this page with non-recovery links.

-    if (accessToken && refreshToken) {
+    const type = params.get('type');
+    if (type === 'recovery' && accessToken && refreshToken) {
       (async () => {
         try {
           const { error } = await supabase.auth.setSession({
             access_token: accessToken,
             refresh_token: refreshToken,
           });
           if (error) {
             toast.error("Error setting session: " + error.message);
             navigate('/login', { replace: true });
           }
         } catch {
           toast.error("Error setting session");
           navigate('/login', { replace: true });
         } finally {
           clearUrlHash();
         }
       })();
     } else {
-      toast.error("Access denied");
+      toast.error("Invalid or expired recovery link");
       navigate('/login', { replace: true });
       clearUrlHash();
     }

98-101: Fix grammar in mismatch message

-      toast.error("Passwords doesn't match. Try Again");
+      toast.error("Passwords don't match. Try again.");

146-161: Improve UX and browser behavior: add autocomplete, minlength, and aria-labels

These help password managers and accessibility, and align with your validation rules.

             <InputField
               icon={Key}
               type="password"
               name="password-1"
-              className="mb-7"
+              className="mb-7"
               placeholder="New Password"
+              aria-label="New password"
+              autoComplete="new-password"
+              minLength={8}
               required
             />
             <InputField
               icon={Key}
               type="password"
               name="password-2"
-              className="mb-7"
+              className="mb-7"
               placeholder="Reenter Password"
+              aria-label="Confirm new password"
+              autoComplete="new-password"
+              minLength={8}
               required
             />

135-135: Mount a single Toaster at the app root

Consider moving <Toaster /> to a top-level (e.g., App.tsx) to avoid multiple instances across pages and reduce DOM duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2cdb0 and aa002d5.

📒 Files selected for processing (4)
  • frontend/src/App.tsx (3 hunks)
  • frontend/src/components/pages/ForgotPasswrdPage.tsx (1 hunks)
  • frontend/src/components/pages/ResetPasswordPage.tsx (1 hunks)
  • frontend/src/components/pages/SignUpPage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/components/pages/ForgotPasswrdPage.tsx
  • frontend/src/App.tsx
  • frontend/src/components/pages/SignUpPage.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/src/components/pages/ResetPasswordPage.tsx (2)

2-2: Import React types explicitly and avoid React. namespace references

Matches prior feedback: import the types and update the interface to prevent namespace issues in strict TS configs.

-import type { ReactNode, FormEvent } from "react";
+import type { ReactNode, FormEvent, InputHTMLAttributes, ElementType } from "react";
@@
-interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  icon: React.ElementType;
+interface InputFieldProps extends InputHTMLAttributes<HTMLInputElement> {
+  icon: ElementType;
 }

Also applies to: 17-19


35-45: Preserve caller-provided className in InputField

Current {...props} + fixed className drops caller classes (e.g., Line 155, 163). Merge them.

-const InputField = ({ icon: Icon, ...props }: InputFieldProps) => (
+const InputField = ({ icon: Icon, className, ...props }: InputFieldProps) => (
   <div className="relative">
     <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
       <Icon className="h-5 w-5 text-gray-400" />
     </div>
     <input
-      {...props}
-      className="block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
+      {...props}
+      className={`block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent ${className ?? ""}`}
     />
   </div>
 );
🧹 Nitpick comments (3)
frontend/src/components/pages/ResetPasswordPage.tsx (3)

98-101: Use strict comparison and fix grammar

Avoid type-coercing != and tweak the message.

-    if (password != repassword) {
-      toast.error("Passwords doesn't match. Try Again");
+    if (password !== repassword) {
+      toast.error("Passwords don't match. Try again.");
       return;
     }

118-121: Refine “special character” check (spaces currently count as special)

/[^A-Za-z0-9]/ matches spaces and other whitespace. If spaces shouldn’t satisfy the rule, use a stricter check.

-    if (!/[^A-Za-z0-9]/.test(password)) {
-      toast.error("Password must contain at least one special character.");
+    if (!/[^\w\s]/.test(password)) {
+      toast.error("Password must contain at least one non-alphanumeric symbol (excluding spaces).");
       return;
     }

If you prefer an allowlist, we can switch to an explicit symbol set (e.g., !@#$...). Confirm desired policy and I’ll adjust.


151-166: Improve UX and browser integration for password fields

Add autoComplete="new-password" and minLength hints; consider ARIA labels.

             <InputField
               icon={Key}
               type="password"
               name="password-1"
               className="mb-7"
               placeholder="New Password"
+              autoComplete="new-password"
+              minLength={8}
+              aria-label="New password"
               required
             />
             <InputField
               icon={Key}
               type="password"
               name="password-2"
               className="mb-7"
               placeholder="Reenter Password"
+              autoComplete="new-password"
+              minLength={8}
+              aria-label="Confirm new password"
               required
             />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa002d5 and ece5133.

📒 Files selected for processing (1)
  • frontend/src/components/pages/ResetPasswordPage.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/components/pages/ResetPasswordPage.tsx (2)
frontend/src/lib/supabaseClient.tsx (1)
  • supabase (12-12)
backend/app/core/config/settings.py (1)
  • Settings (8-51)
🪛 Biome (2.1.2)
frontend/src/components/pages/ResetPasswordPage.tsx

[error] 202-202: expected } but instead the file ends

the file ends here

(parse)

🔇 Additional comments (1)
frontend/src/components/pages/ResetPasswordPage.tsx (1)

122-135: Good: robust loading state and success feedback around updateUser

This addresses prior feedback: loading is reset via finally, errors are handled, and success shows a toast before navigating.

Copy link
Contributor

@smokeyScraper smokeyScraper left a comment

Choose a reason for hiding this comment

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

A few things to be taken care of -

  • Even if the user has never registered, the forgot password option still sends a mail to them. However it does nothing and just redirects to login.
  • Could you please add on a logout button too in the profile section. The only way to logout currently is by clearing browser cache.
  • Auth state not subscribed globally (can break post-reset flow). Only an initial getSession() runs; the app doesn't react to token changes (confirm email, password reset, sign-out in other tabs). Something like onAuthStateChange should be implemented.
  • Multiple toasters being rendered throughout. There's a global toaster in App. Please keep a single global toaster.
  • supabaseClient.tsx contains no JSX; rename to supabaseClient.ts.
  • .env example is missing VITE_BASE_URL.
  • reset-password seems to be not implement. It can probably be added in the profile section.

Could you please resolve 1, 3, 4, 5, 6.
Rest for 2 and 7 separate issue can be raised.

Thanks a lot for you contribution. Rest PR seems fine to me.

<p className="text-center text-gray-400 text-sm">
<button
type="button"
onClick={() => navigate('/signup')}
Copy link
Contributor

Choose a reason for hiding this comment

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

this should re-direct to sign in. It says "Back to SignIn" but redirect to SignUp

@PinJinx PinJinx requested a review from smokeyScraper August 13, 2025 14:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
frontend/src/components/pages/ForgotPasswrdPage.tsx (1)

115-121: Back-to-sign-in route is now correct — thanks for fixing

This resolves the earlier review comment about the button routing to /signup.

Also applies to: 133-138

🧹 Nitpick comments (13)
frontend/src/lib/supabaseClient.ts (3)

4-9: Fail-fast throw is fine; add a guard against leaking service_role keys and consider a softer UX in prod.

  • Add a lightweight runtime check to block accidental use of a service_role key in the browser.
  • Optional: in production builds, prefer rendering a friendly error screen instead of throwing at module load (which white-screens the app).

Apply:

 const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "";
 const supabaseAnonKey = import.meta.env.VITE_SUPABASE_KEY || "";

-if (!supabaseUrl || !supabaseAnonKey) {
+// Prevent accidental use of a service_role key in the browser
+if (typeof window !== 'undefined' && supabaseAnonKey) {
+  try {
+    const payload = JSON.parse(atob((supabaseAnonKey.split('.')[1] || '')));
+    if (payload?.role === 'service_role') {
+      throw new Error('Refusing to initialize Supabase with a service_role key in the browser. Use the anon public key instead.');
+    }
+  } catch {
+    // noop: non-JWT or decoding failure; continue
+  }
+}
+
+if (!supabaseUrl || !supabaseAnonKey) {
   throw new Error('Missing Supabase configuration. Please set VITE_SUPABASE_URL and VITE_SUPABASE_KEY environment variables.');
 }

12-12: Explicit auth options for SPA stability.

Defaults are OK, but being explicit helps future readers and avoids regressions.

-export const supabase = createClient(supabaseUrl, supabaseAnonKey)
+export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
+  auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true },
+})

4-6: Env var usage verified

  • No occurrences of service_role or related keys found in the codebase.
  • Recommend updating documentation to clarify that VITE_SUPABASE_KEY must be the anon public key, never a service_role key.
frontend/src/components/pages/ProfilePage.tsx (2)

182-194: Minor: make the button type explicit and add an accessible label.

Prevents unintended form submissions if this moves inside a form later, and improves a11y.

-              <motion.button
+              <motion.button
+                type="button"
                 whileHover={{ scale: 1.05 }}
                 whileTap={{ scale: 0.95 }}
                 onClick={() => {
                   onSignOut();
                 }}
                 className="px-4 py-2 bg-red-500 hover:bg-red-600 rounded-lg transition-colors flex items-center"
+                aria-label="Sign out"
               >
                 <DoorClosed size={18} className="mr-2" />
                 Sign Out
               </motion.button>

4-4: Icon choice nit.

DoorClosed works, but LogOut better matches the intent. Optional swap.

frontend/src/App.tsx (3)

29-36: Clarify session-check error message and set landing page post-restore.

  • Current toast reads “User Login Failed” on a session lookup error, which is misleading.
  • When a session exists, optionally default users to the dashboard to match the linked issue’s objective.
-    supabase.auth.getSession().then(({ data, error }) => {
-      if (error) {
-        toast.error('User Login Failed');
+    supabase.auth.getSession().then(({ data, error }) => {
+      if (error) {
+        toast.error('Failed to restore session');
         console.error('Error checking session:', error);
         return;
       }
-      setIsAuthenticated(!!data.session);
+      const hasSession = !!data.session;
+      setIsAuthenticated(hasSession);
+      if (hasSession) setActivePage('dashboard');
     });

38-66: Tighten subscription destructuring and navigate to dashboard on sign-in; also avoid double toasts.

  • Destructure subscription directly and clean up with subscription.unsubscribe().
  • Set activePage to 'dashboard' on SIGNED_IN.
  • Since SIGNED_OUT already toasts here, drop the success toast from handleLogout to prevent duplicates.
-    const { data: subscription } = supabase.auth.onAuthStateChange(
-      (event, session) => {
+    const { data: { subscription } } = supabase.auth.onAuthStateChange(
+      (event, session) => {
         console.log("Auth event:", event, session);
         switch(event){
         case "SIGNED_IN":
           setIsAuthenticated(true);
+          setActivePage('dashboard');
           toast.success("Signed in!");
           break;

         case "SIGNED_OUT":
           setIsAuthenticated(false);
           setActivePage("landing");
           setRepoData(null);
           toast.success("Signed out!");
           break;
@@
-    return () => {
-      subscription.subscription.unsubscribe();
-    };
+    return () => {
+      subscription.unsubscribe();
+    };

Also applies to: 67-69


77-85: Avoid duplicate logout state updates and toasts; rely on onAuthStateChange.

Let the SIGNED_OUT handler manage UI state and success toast.

   const handleLogout = async () => {
     const { error } = await supabase.auth.signOut();
     if (error) {
       toast.error('Logout failed');
       console.error('Error during logout:', error);
-      return;
+      return;
     }
-    toast.success('Signed out!');
-    setIsAuthenticated(false);
-    setActivePage('landing');
-    setRepoData(null);
   };
frontend/src/components/pages/ForgotPasswrdPage.tsx (5)

1-1: Use explicit React type imports and honor caller className in InputField

  • Import React types directly to avoid “Cannot find namespace 'React'.” in some TS configs.
  • Merge className from props; currently it’s ignored because the component overwrites it.
-import { useState, ReactNode, FormEvent } from "react";
+import { useState } from "react";
+import type { ReactNode, FormEvent, InputHTMLAttributes, ElementType } from "react";
@@
-interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  icon: React.ElementType;
+interface InputFieldProps extends InputHTMLAttributes<HTMLInputElement> {
+  icon: ElementType;
 }
@@
-const InputField = ({ icon: Icon, ...props }: InputFieldProps) => (
+const InputField = ({ icon: Icon, className, ...props }: InputFieldProps) => (
   <div className="relative">
     <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
       <Icon className="h-5 w-5 text-gray-400" />
     </div>
     <input
-      {...props}
-      className="block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
+      {...props}
+      className={`block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent ${className ?? ''}`}
     />
   </div>
 );

Also applies to: 14-20, 36-46


85-92: Minor a11y and UX: add autocomplete and aria-label to email field

Improves accessibility and browser autofill.

               <InputField
                 icon={Mail}
                 type="email"
                 name="email"
-                className="mb-7"
+                className="mb-7"
                 placeholder="Email address"
+                aria-label="Email address"
+                autoComplete="email"
                 required
               />

49-49: Fix spelling: rename component to ForgotPasswordPage

Keeps names consistent and easier to grep.

-export default function ForgotPasswrdPage() {
+export default function ForgotPasswordPage() {

If routing imports rely on the default export only, this change is non-breaking. If the filename is also misspelled, consider renaming the file in a follow-up.


134-134: Remove redundant state update before navigation

navigate('/login') will unmount this component; setMailPage(false) is unnecessary.

-              onClick={() => { setMailPage(false); navigate('/login') }}
+              onClick={() => { navigate('/login') }}

4-4: Import spacing nit

Tiny formatting fix keeps imports consistent with the rest of the codebase.

-import { toast} from "react-hot-toast";
+import { toast } from "react-hot-toast";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 815e99b and 9dd1cdf.

📒 Files selected for processing (6)
  • frontend/src/App.tsx (4 hunks)
  • frontend/src/components/pages/ForgotPasswrdPage.tsx (1 hunks)
  • frontend/src/components/pages/ProfilePage.tsx (2 hunks)
  • frontend/src/components/pages/ResetPasswordPage.tsx (1 hunks)
  • frontend/src/components/pages/SignUpPage.tsx (1 hunks)
  • frontend/src/lib/supabaseClient.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/pages/ResetPasswordPage.tsx
  • frontend/src/components/pages/SignUpPage.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/App.tsx (4)
frontend/src/lib/supabaseClient.ts (1)
  • supabase (12-12)
frontend/src/components/pages/ForgotPasswrdPage.tsx (1)
  • ForgotPasswrdPage (49-144)
frontend/src/components/pages/ResetPasswordPage.tsx (1)
  • ResetPasswordPage (50-201)
frontend/src/components/pages/SignUpPage.tsx (1)
  • SignUpPage (51-210)
frontend/src/components/pages/ForgotPasswrdPage.tsx (1)
frontend/src/lib/supabaseClient.ts (1)
  • supabase (12-12)
🔇 Additional comments (6)
frontend/src/components/pages/ProfilePage.tsx (1)

6-6: Prop API change looks good.

App.tsx wires onSignOut correctly. No issues.

frontend/src/App.tsx (5)

4-4: Toaster import/use LGTM.

Placement at top-right is fine.


16-19: Auth routes/components imports are fine.

Nothing to flag.


109-109: ProfilePage wiring LGTM.

Prop passed correctly.


130-153: Route gating looks correct.

  • forgot-password and signup are public when unauthenticated.
  • reset-password accessible as intended.

73-76: Potential duplication with onAuthStateChange.

If LoginPage already signs in via Supabase, onAuthStateChange will set isAuthenticated; consider removing handleLogin or ensure it’s only called after a successful sign-in to avoid flicker.

Copy link
Contributor

@smokeyScraper smokeyScraper left a comment

Choose a reason for hiding this comment

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

hey @PinJinx, can you please address the recent coderrabit issue? If that seems something relevant please do the needful.

Also please can you please change the environment variable name to
SUPABASE_URL from VITE_SUPABASE_URL and SUPABASE_KEY from VITE_SUPABASE_KEY as both of these are same.

Also if possible try to restructure the commits a bit,
In order to have a clean commit history, please undo your last commits and make changes within that, then make a commit with a force push. This keeps the commit history clean. I'm preferring "'n' commits for edits" + "1 commit for coderabbit fix" (Easiest is to use editor's (VS Code/Cursor) GitHub section for this)

@smokeyScraper
Copy link
Contributor

hey @PinJinx , you might have to rebase on top of #127, also please the placement for the logout button was much better in #127 , can you please align as per that?
Thanks!

DhruvK278 and others added 9 commits September 8, 2025 13:53
Migrates Discord bot commands from legacy prefix-based to modern slash commands using discord.py app_commands. Refactors DevRelCommands cog to use slash commands, improves token cleanup task management, and updates verification flows for ephemeral responses. Discord bot now loads cogs dynamically during async startup, and the Discord dependency is added to pyproject.toml.
feat: Added env example

feat: Added Forgot Password Page
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
frontend/src/components/pages/ResetPasswordPage.tsx (2)

2-2: Import React types explicitly; avoid React namespace usage

Prevents “Cannot find namespace 'React'” in stricter TS configs.

-import type { ReactNode, FormEvent } from "react";
+import type { ReactNode, FormEvent, InputHTMLAttributes, ElementType } from "react";
@@
-interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  icon: React.ElementType;
+interface InputFieldProps extends InputHTMLAttributes<HTMLInputElement> {
+  icon: ElementType;
 }

Also applies to: 17-19


35-45: Preserve caller-provided className in InputField

Current order overrides consumer classes (e.g., mb-7).

-const InputField = ({ icon: Icon, ...props }: InputFieldProps) => (
+const InputField = ({ icon: Icon, className, ...props }: InputFieldProps) => (
   <div className="relative">
@@
-    <input
-      {...props}
-      className="block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
-    />
+    <input
+      {...props}
+      className={`block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent ${className ?? ""}`}
+    />
   </div>
 );
🧹 Nitpick comments (26)
frontend/src/components/pages/ResetPasswordPage.tsx (5)

96-97: Avoid unsafe cast from FormData.get; coerce safely to string

Prevents a runtime error if a field is unexpectedly missing.

-    const password = formData.get('password-1') as string;
-    const repassword = formData.get('password-2') as string;
+    const password = formData.get('password-1')?.toString() ?? "";
+    const repassword = formData.get('password-2')?.toString() ?? "";

98-101: Use strict equality and fix grammar in toast

Tiny correctness/readability win.

-    if (password != repassword) {
-      toast.error("Passwords doesn't match. Try Again");
+    if (password !== repassword) {
+      toast.error("Passwords don't match. Try again.");

56-67: Constrain reset flow to recovery links only

Check the type param to ensure we only accept recovery tokens.

-    const params = new URLSearchParams(window.location.hash.slice(1));
+    const params = new URLSearchParams(window.location.hash.slice(1));
@@
-    const accessToken = params.get('access_token');
-    const refreshToken = params.get('refresh_token');
+    const accessToken = params.get('access_token');
+    const refreshToken = params.get('refresh_token');
+    const linkType = params.get('type');
@@
-    if (accessToken && refreshToken) {
+    if (accessToken && refreshToken && linkType === 'recovery') {

151-165: Improve UX/accessibility: autocomplete, minLength, aria-labels

Helps browsers/autofill and screen readers.

             <InputField
               icon={Key}
               type="password"
               name="password-1"
               className="mb-7"
               placeholder="New Password"
+              autoComplete="new-password"
+              minLength={8}
+              aria-label="New password"
               required
             />
             <InputField
               icon={Key}
               type="password"
               name="password-2"
               className="mb-7"
               placeholder="Reenter Password"
+              autoComplete="new-password"
+              minLength={8}
+              aria-label="Reenter new password"
               required
             />

166-172: Expose loading state to assistive tech

Advertise progress on the submit button.

             <motion.button
               whileHover={{ scale: 1.02 }}
               whileTap={{ scale: 0.98 }}
               type="submit"
               disabled={isLoading}
+              aria-busy={isLoading}
               className="w-full py-3 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center"
             >
backend/app/agents/devrel/prompts/response_prompt.py (1)

33-40: Add one more constraint for deterministic lists.

Consider specifying score formatting to keep outputs consistent.

 SPECIAL FORMATTING FOR CONTRIBUTOR RECOMMENDATIONS:
 If the task result contains contributor recommendations:
 - Start with "Found X Contributors" 
 - Show search query used and keywords
-- For each contributor: "1. username (Score: X.XXX)"
+- For each contributor: "1. username (Score: X.XXX)" — format scores to 3 decimals
 - Include their expertise/reason for recommendation
 - End with metadata about search and actionable guidance
backend/integrations/discord/bot.py (5)

39-43: Consider more specific exception handling for slash command sync.

While catching the broad Exception helps prevent startup failures, consider catching more specific exceptions (e.g., discord.HTTPException, discord.Forbidden) to better understand and handle different failure scenarios.

 try:
     synced = await self.tree.sync()
     print(f"Synced {len(synced)} slash command(s)")
-except Exception as e:
+except discord.HTTPException as e:
+    print(f"Failed to sync slash commands due to HTTP error: {e}")
+except discord.Forbidden as e:
+    print(f"Failed to sync slash commands due to permissions: {e}")
+except Exception as e:
     print(f"Failed to sync slash commands: {e}")

91-94: Consider extracting priority mapping as a class constant.

The priority mapping is a static configuration that could be defined once at the class level rather than recreated on each message.

Add this as a class constant:

PRIORITY_MAP = {
    "high": QueuePriority.HIGH,
    "medium": QueuePriority.MEDIUM,
    "low": QueuePriority.LOW
}

Then use it in the method:

-priority_map = {"high": QueuePriority.HIGH,
-                "medium": QueuePriority.MEDIUM,
-                "low": QueuePriority.LOW
-}
-priority = priority_map.get(triage_result.get("priority"), QueuePriority.MEDIUM)
+priority = self.PRIORITY_MAP.get(triage_result.get("priority"), QueuePriority.MEDIUM)

105-106: Use logger.exception for better error tracking.

When catching exceptions in an except block, use logger.exception instead of logger.error to automatically include the traceback.

 except Exception as e:
-    logger.error(f"Error handling DevRel message: {str(e)}")
+    logger.exception("Error handling DevRel message")

125-127: Use logger.exception for better error tracking.

Similar to the previous comment, use logger.exception for automatic traceback inclusion.

 except Exception as e:
-    logger.error(f"Failed to create thread: {e}")
+    logger.exception("Failed to create thread")

141-142: Use logger.exception for better error tracking.

 except Exception as e:
-    logger.error(f"Error handling agent response: {str(e)}")
+    logger.exception("Error handling agent response")
backend/integrations/discord/cogs.py (3)

36-37: Consider more specific exception handling.

While catching broad exceptions prevents task failures from crashing the bot, consider catching more specific exceptions or at least logging the full traceback.

 except Exception as e:
-    logger.error(f"Error during token cleanup: {e}")
+    logger.exception("Error during token cleanup")

99-101: Use logger.exception for better error tracking.

 except Exception as e:
-    logger.error(f"Error checking verification status: {e}")
+    logger.exception("Error checking verification status")
     await interaction.response.send_message("❌ Error checking verification status.", ephemeral=True)

172-173: Use logger.exception for better error tracking.

 except Exception as e:
-    logger.error(f"Error in /verify_github: {e}")
+    logger.exception("Error in /verify_github command")
backend/main.py (5)

47-53: Decide on fail-fast vs. degrade if the Discord cog fails to load

You log and continue when the extension fails. If commands are critical, fail startup; if optional, at least surface a health signal.

Apply either approach:

Option A (fail-fast):

-            try:
-                await self.discord_bot.load_extension("integrations.discord.cogs")
-            except (ImportError, commands.ExtensionError) as e:
-                logger.error("Failed to load Discord cog extension: %s", e)
+            try:
+                await self.discord_bot.load_extension("integrations.discord.cogs")
+            except (ImportError, commands.ExtensionError):
+                logger.exception("Failed to load Discord cog extension")
+                raise

Option B (degrade, but expose status):

  • Track a boolean self.discord_ext_loaded and expose it via a health endpoint/metric so operators know commands are unavailable.

59-61: Prefer logger.exception in except blocks

It automatically includes the traceback and satisfies TRY400.

-            logger.error(f"Error during background task startup: {e}", exc_info=True)
+            logger.exception("Error during background task startup")

63-71: Treat Weaviate not-ready as a startup failure (or log clearly)

Currently you only log when ready; a False readiness silently proceeds.

-                if await client.is_ready():
-                    logger.info("Weaviate connection successful and ready")
+                if await client.is_ready():
+                    logger.info("Weaviate connection successful and ready")
+                else:
+                    logger.error("Weaviate connected but not ready")
+                    raise RuntimeError("Weaviate not ready at startup")

If Weaviate is optional, at least log a warning and set a health flag.


77-81: Shutdown path is good; align logging to exception helper

Small consistency tweak.

-        except Exception as e:
-            logger.error(f"Error closing Discord bot: {e}", exc_info=True)
+        except Exception:
+            logger.exception("Error closing Discord bot")

Do the same for the queue manager block.


125-130: Uvicorn target is fine for script runs; ensure env validation covers ASGI runs

Env checks run only in main. If deployed via uvicorn backend.main:api, missing secrets won’t be validated. Consider validating critical settings in start_background_tasks.

backend/app/agents/devrel/github/github_toolkit.py (1)

9-9: Tool import enabled; consider lazy import to reduce cold-start and avoid cycles

Delay heavy/optional deps until actually used.

- from .tools.contributor_recommendation import handle_contributor_recommendation
+ # Lazy import in execute() to reduce import-time coupling

And inside the branch:

from .tools.contributor_recommendation import handle_contributor_recommendation  # local import
backend/app/agents/devrel/github/prompts/contributor_recommendation/issue_summarization.py (1)

14-20: Delimit injected issue content to reduce prompt-injection risk and improve LLM parsing

Wrap {issue_content} in explicit code fences so the model treats it as verbatim context. This also helps when the issue body contains JSON-like braces.

-**GitHub Issue Content:**
----
-{issue_content}
----
+**GitHub Issue Content (verbatim):**
+---
+```markdown
+{issue_content}
+```
+---
 
 **Optimized Technical Summary for Contributor Search:**
backend/app/agents/devrel/github/tools/contributor_recommendation.py (4)

36-43: Bracket GitHub issue content when composing the alignment prompt

Use explicit sentinels to segregate raw issue text from the user’s query. This improves robustness of downstream JSON alignment.

-        if url_match:
-            issue_content = await self._fetch_github_issue_content(url_match.group(0))
-            full_query = f"{query}\n\nIssue content: {issue_content}"
+        if url_match:
+            issue_content = await self._fetch_github_issue_content(url_match.group(0))
+            full_query = (
+                f"{query}\n\n[BEGIN_GITHUB_ISSUE]\n{issue_content}\n[END_GITHUB_ISSUE]"
+            )
         else:
             full_query = query

77-79: Log with traceback and re-raise without shadowing

Use logger.exception to capture the stack trace and prefer bare raise to preserve the original context.

-        except Exception as e:
-            logger.error(f"GitHub issue fetching failed: {e}")
-            raise
+        except Exception:
+            logger.exception("GitHub issue fetching failed")
+            raise

100-106: Make search weighting configurable via settings

Hard-coded weights make tuning difficult. Consider moving these to config (e.g., settings.vector_weight, settings.bm25_weight) with sensible defaults.


168-170: Use logger.exception for unified error reporting

This captures the traceback without needing exc_info=True, and avoids manual string conversion.

-    except Exception as e:
-        logger.error(f"Error in contributor recommendation: {str(e)}", exc_info=True)
-        return {"status": "error", "message": str(e)}
+    except Exception as e:
+        logger.exception("Error in contributor recommendation")
+        return {"status": "error", "message": str(e)}
backend/app/services/github/issue_processor.py (1)

82-83: Prefer logger.exception and bare raise

Keeps full traceback and avoids re-wrapping the exception.

-        except Exception as e:
-            logger.error(f"Error processing issue {self.owner}/{self.repo}#{self.issue_number}: {str(e)}")
-            raise e
+        except Exception:
+            logger.exception("Error processing issue %s/%s#%s", self.owner, self.repo, self.issue_number)
+            raise
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c73013c and a1346cb.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • .vimrc (1 hunks)
  • backend/app/agents/devrel/github/github_toolkit.py (2 hunks)
  • backend/app/agents/devrel/github/prompts/contributor_recommendation/issue_summarization.py (1 hunks)
  • backend/app/agents/devrel/github/prompts/contributor_recommendation/query_alignment.py (1 hunks)
  • backend/app/agents/devrel/github/prompts/intent_analysis.py (2 hunks)
  • backend/app/agents/devrel/github/tools/contributor_recommendation.py (1 hunks)
  • backend/app/agents/devrel/prompts/response_prompt.py (1 hunks)
  • backend/app/api/v1/auth.py (1 hunks)
  • backend/app/services/github/issue_processor.py (1 hunks)
  • backend/app/services/github/user/profiling.py (1 hunks)
  • backend/integrations/discord/bot.py (4 hunks)
  • backend/integrations/discord/cogs.py (7 hunks)
  • backend/main.py (2 hunks)
  • backend/requirements.txt (1 hunks)
  • docs/INSTALL_GUIDE.md (1 hunks)
  • frontend/.env example (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/App.tsx (4 hunks)
  • frontend/src/components/pages/ForgotPasswrdPage.tsx (1 hunks)
  • frontend/src/components/pages/LoginPage.tsx (4 hunks)
  • frontend/src/components/pages/ProfilePage.tsx (2 hunks)
  • frontend/src/components/pages/ResetPasswordPage.tsx (1 hunks)
  • frontend/src/components/pages/SignUpPage.tsx (1 hunks)
  • frontend/src/lib/supabaseClient.ts (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • backend/app/api/v1/auth.py
  • .vimrc
🚧 Files skipped from review as they are similar to previous changes (10)
  • docs/INSTALL_GUIDE.md
  • frontend/src/components/pages/LoginPage.tsx
  • frontend/.env example
  • frontend/src/components/pages/SignUpPage.tsx
  • frontend/package.json
  • frontend/src/lib/supabaseClient.ts
  • frontend/src/components/pages/ForgotPasswrdPage.tsx
  • frontend/src/App.tsx
  • backend/requirements.txt
  • frontend/src/components/pages/ProfilePage.tsx
🧰 Additional context used
🧬 Code graph analysis (7)
backend/app/agents/devrel/github/tools/contributor_recommendation.py (3)
backend/app/database/weaviate/operations.py (1)
  • search_contributors (366-379)
backend/app/services/github/issue_processor.py (2)
  • GitHubIssueProcessor (14-83)
  • fetch_issue_content (30-56)
backend/app/services/embedding_service/service.py (3)
  • EmbeddingService (29-226)
  • model (41-52)
  • get_embedding (70-90)
backend/app/agents/devrel/github/github_toolkit.py (1)
backend/app/agents/devrel/github/tools/contributor_recommendation.py (1)
  • handle_contributor_recommendation (81-170)
backend/app/services/github/issue_processor.py (2)
backend/app/services/embedding_service/service.py (3)
  • EmbeddingService (29-226)
  • model (41-52)
  • get_embedding (70-90)
backend/app/services/github/user/profiling.py (2)
  • GitHubUserProfiler (15-298)
  • request (77-79)
backend/main.py (2)
backend/app/core/orchestration/queue_manager.py (1)
  • start (47-56)
backend/app/database/weaviate/client.py (1)
  • get_weaviate_client (19-32)
frontend/src/components/pages/ResetPasswordPage.tsx (1)
frontend/src/lib/supabaseClient.ts (1)
  • supabase (12-12)
backend/integrations/discord/bot.py (2)
backend/app/classification/classification_router.py (1)
  • ClassificationRouter (10-59)
backend/app/core/orchestration/queue_manager.py (2)
  • QueuePriority (12-15)
  • enqueue (73-92)
backend/integrations/discord/cogs.py (3)
backend/integrations/discord/bot.py (1)
  • on_ready (35-43)
backend/app/services/auth/management.py (1)
  • get_or_create_user_by_discord (10-41)
backend/app/services/auth/verification.py (1)
  • create_verification_session (33-61)
🪛 Ruff (0.12.2)
backend/app/agents/devrel/github/tools/contributor_recommendation.py

52-52: Consider moving this statement to an else block

(TRY300)


75-75: Abstract raise to an inner function

(TRY301)


75-75: Avoid specifying long messages outside the exception class

(TRY003)


78-78: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


169-169: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backend/app/services/github/issue_processor.py

41-41: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Abstract raise to an inner function

(TRY301)


74-74: Avoid specifying long messages outside the exception class

(TRY003)


80-80: Consider moving this statement to an else block

(TRY300)


82-82: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


82-82: Use explicit conversion flag

Replace with conversion flag

(RUF010)


83-83: Use raise without specifying exception name

Remove exception name

(TRY201)

backend/main.py

51-51: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


54-56: Store a reference to the return value of asyncio.create_task

(RUF006)

backend/integrations/discord/bot.py

42-42: Do not catch blind exception: Exception

(BLE001)


105-105: Do not catch blind exception: Exception

(BLE001)


106-106: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


106-106: Use explicit conversion flag

Replace with conversion flag

(RUF010)


125-125: Do not catch blind exception: Exception

(BLE001)


126-126: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


141-141: Do not catch blind exception: Exception

(BLE001)


142-142: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


142-142: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backend/integrations/discord/cogs.py

99-99: Do not catch blind exception: Exception

(BLE001)


100-100: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


172-172: Do not catch blind exception: Exception

(BLE001)


173-173: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🔇 Additional comments (15)
frontend/src/components/pages/ResetPasswordPage.tsx (1)

67-84: Session setup + hash clearing look good

Nice use of try/catch/finally with replaceState to drop tokens.

backend/app/agents/devrel/prompts/response_prompt.py (1)

29-29: LGTM: simpler emoji guidance.

Dropping the hardcoded emoji list reduces prompt brittleness across channels.

backend/app/agents/devrel/github/prompts/contributor_recommendation/query_alignment.py (1)

1-37: LGTM! Well-structured prompt for query alignment.

The prompt template effectively guides the transformation of user requests into technical search queries, with clear examples and strict formatting rules to ensure consistent JSON output.

backend/integrations/discord/bot.py (3)

21-21: Good migration to slash commands.

Setting command_prefix=None properly indicates this bot uses slash commands exclusively, aligning with modern Discord bot best practices.


46-51: Smart filtering of slash command interactions.

Good use of interaction_metadata to properly distinguish and skip slash command interactions, preventing double-processing of commands.


137-138: Efficient message chunking implementation.

Good implementation of Discord's 2000-character message limit handling with a simple loop.

backend/integrations/discord/cogs.py (5)

20-24: Good implementation of task lifecycle management.

The on_ready listener properly checks if the cleanup task is already running before starting it, preventing duplicate task instances.


44-55: Clean implementation of the reset command.

The slash command properly handles user context, enqueues the cleanup task with high priority, and provides user feedback via ephemeral message.


57-74: Well-structured help command with clear formatting.

The help embed provides clear documentation of available commands with proper slash command syntax.


106-106: Good use of deferred response for long-running operations.

Deferring the response with ephemeral=True is the correct approach for operations that may take longer than Discord's 3-second timeout.


181-183: Proper cog setup function implementation.

The setup function correctly implements Discord.py's extension loading pattern, properly registering the cog with the bot.

backend/main.py (1)

15-17: Import for ExtensionError looks correct

Using discord.ext.commands.ExtensionError is valid for extension load failures. No action needed.

backend/app/agents/devrel/github/github_toolkit.py (1)

104-106: Route looks correct; ensure consistent result shape and failure handling

handle_contributor_recommendation returns a dict with status/message; you already attach intent/type. LGTM.

Optionally add a guard to normalize non-dict returns:

if not isinstance(result, dict):
    result = {"status": "error", "message": "Invalid tool response", "raw": str(result)}
backend/app/agents/devrel/github/prompts/intent_analysis.py (2)

5-5: Clearer function description for contributor_recommendation

Updated scope is crisp and will improve routing. LGTM.


15-21: Good, concrete examples improve LLM accuracy

The examples cover PRs, tech domains, and issue URLs, which should reduce misclassification.

Comment on lines 47 to 53
try:
import json
print(response)
result = json.loads(response.content.strip())
logger.info(f"Query aligned: '{result.get('aligned_query')}' with keywords: {result.get('keywords')}")
return result
except json.JSONDecodeError:
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace print with structured logging and tolerate fenced JSON from the LLM

Printing backend objects is noisy and can leak data in some environments. Also, LLMs often wrap JSON in triple backticks; parse that defensively before json.loads.

         try:
-            import json
-            print(response)
-            result = json.loads(response.content.strip())
-            logger.info(f"Query aligned: '{result.get('aligned_query')}' with keywords: {result.get('keywords')}")
+            import json
+            raw = response.content or ""
+            logger.debug("Alignment raw response (first 500 chars): %s", raw[:500])
+            match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', raw, re.S)
+            payload = match.group(1) if match else raw.strip()
+            result = json.loads(payload)
+            logger.info("Query aligned: '%s' with keywords: %s", result.get("aligned_query"), result.get("keywords"))
             return result

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.12.2)

52-52: Consider moving this statement to an else block

(TRY300)

🤖 Prompt for AI Agents
In backend/app/agents/devrel/github/tools/contributor_recommendation.py around
lines 47 to 53, replace the raw print(response) with structured logger calls and
defensively handle LLM-wrapped JSON: strip surrounding triple backticks or other
fencing before calling json.loads by using a regex to extract the innermost JSON
object (e.g., find a { ... } block) or fallback to the stripped raw content,
then call json.loads on that payload; use logger.info with structured arguments
instead of f-strings/print to log the aligned_query and keywords, and ensure the
JSONDecodeError except branch logs the error and the raw payload for debugging.

Comment on lines 39 to 56
issue_data = await profiler.request(issue_url)
if not issue_data:
raise ValueError("Failed to fetch issue data.")

content_parts = [
f"Title: {issue_data['title']}",
f"Body: {issue_data['body']}",
]

comments_data = await profiler.request(comments_url)
if comments_data:
comment_texts = [
f"Comment by {c['user']['login']}: {c['body']}"
for c in comments_data if c.get('body')
]
content_parts.extend(comment_texts)

return "\n\n---\n\n".join(content_parts)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Defensive field access and length caps to keep prompts within model limits

GitHub payloads can have missing fields and very long bodies. Use .get(...) with defaults and cap sizes; also limit the number/size of comments.

-            content_parts = [
-                f"Title: {issue_data['title']}",
-                f"Body: {issue_data['body']}",
-            ]
-
-            comments_data = await profiler.request(comments_url)
-            if comments_data:
-                comment_texts = [
-                    f"Comment by {c['user']['login']}: {c['body']}"
-                    for c in comments_data if c.get('body')
-                ]
-                content_parts.extend(comment_texts)
-
-            return "\n\n---\n\n".join(content_parts)
+            # Be defensive about missing fields and cap lengths to contain token usage
+            title = (issue_data.get("title") or "")[:300]
+            body = (issue_data.get("body") or "")[:4000]
+            content_parts = [f"Title: {title}", f"Body: {body}"]
+
+            comments_data = await profiler.request(comments_url)
+            if comments_data:
+                comment_texts = []
+                for c in comments_data[:20]:
+                    text = (c.get("body") or "").strip()
+                    if not text:
+                        continue
+                    login = c.get("user", {}).get("login", "unknown")
+                    comment_texts.append(f"Comment by {login}: {text[:1000]}")
+                content_parts.extend(comment_texts)
+
+            return "\n\n---\n\n".join(content_parts)
📝 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
issue_data = await profiler.request(issue_url)
if not issue_data:
raise ValueError("Failed to fetch issue data.")
content_parts = [
f"Title: {issue_data['title']}",
f"Body: {issue_data['body']}",
]
comments_data = await profiler.request(comments_url)
if comments_data:
comment_texts = [
f"Comment by {c['user']['login']}: {c['body']}"
for c in comments_data if c.get('body')
]
content_parts.extend(comment_texts)
return "\n\n---\n\n".join(content_parts)
# Be defensive about missing fields and cap lengths to contain token usage
title = (issue_data.get("title") or "")[:300]
body = (issue_data.get("body") or "")[:4000]
content_parts = [f"Title: {title}", f"Body: {body}"]
comments_data = await profiler.request(comments_url)
if comments_data:
comment_texts = []
for c in comments_data[:20]:
text = (c.get("body") or "").strip()
if not text:
continue
login = c.get("user", {}).get("login", "unknown")
comment_texts.append(f"Comment by {login}: {text[:1000]}")
content_parts.extend(comment_texts)
return "\n\n---\n\n".join(content_parts)
🧰 Tools
🪛 Ruff (0.12.2)

41-41: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In backend/app/services/github/issue_processor.py around lines 39 to 56, the
code directly indexes fields from the GitHub payload and can produce huge
prompts; change to use .get(...) with safe defaults for title, body, and comment
fields, truncate title/body/comment strings to a reasonable length (e.g.
500-2000 chars as appropriate), and limit the number of comments included (e.g.
first N comments) and/or total combined size to ensure prompt stays within model
limits; build content_parts using the safe, truncated values and skip empty
entries.

Comment on lines 77 to 79
async def request(self, url: str, params: Dict | None = None) -> Optional[Dict]:
"""Public, stable wrapper around the internal HTTP helper."""
return await self._make_request(url, params)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

PEP 604 “|” union breaks our Python 3.9 support.

requires-python is >=3.9; the X | Y syntax is only valid on Python 3.10+. Use Optional[Dict] instead. (peps.python.org, docs.python.org)

-    async def request(self, url: str, params: Dict | None = None) -> Optional[Dict]:
+    async def request(self, url: str, params: Optional[Dict] = None) -> Optional[Dict]:

🏁 Script executed:

#!/bin/bash
# Find other occurrences of PEP 604 unions that would break on 3.9
rg -nP --type=py -C1 '\bdef\b[^\n]*\([^)]*\|[^)]*\)'
rg -nP --type=py -C1 '->[^:\n]*\|[^:\n]*:'
rg -nP --type=py -C1 ':\s*[^#\n]*\|[^#\n]*'

Length of output: 2372


PEP 604 union syntax breaks Python 3.9 support
requires-python >= 3.9; the X | Y union syntax is only valid on Python 3.10+. Replace all PEP 604 unions with Optional[...] (or Union[..., None]).

backend/app/services/github/user/profiling.py line 77:

-    async def request(self, url: str, params: Dict | None = None) -> Optional[Dict]:
+    async def request(self, url: str, params: Optional[Dict] = None) -> Optional[Dict]:

backend/app/core/events/base.py line 11: change

channel_id: str | None = None

to

channel_id: Optional[str] = None

Ensure from typing import Optional is imported in each file.

🤖 Prompt for AI Agents
In backend/app/services/github/user/profiling.py around lines 77-79 and
backend/app/core/events/base.py around line 11, replace PEP 604 union syntax (X
| None) with typing.Optional (e.g., Optional[X]) to retain Python 3.9
compatibility, and ensure each file imports Optional from typing; update the
function signature and variable annotations accordingly and remove the
pipe-style unions throughout these files.

Comment on lines 54 to 56
asyncio.create_task(
self.discord_bot.start(settings.discord_bot_token)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Keep a reference to the created task and observe exceptions

Discarding the task can hide startup errors and makes shutdown harder.

-            asyncio.create_task(
-                self.discord_bot.start(settings.discord_bot_token)
-            )
+            self._bot_task = asyncio.create_task(
+                self.discord_bot.start(settings.discord_bot_token)
+            )
+            self._bot_task.add_done_callback(
+                lambda t: logger.exception("Discord bot task errored", exc_info=t.exception())
+                if t.exception() else None
+            )

Outside this hunk, add initialization and shutdown handling:

# __init__
self._bot_task = None
# stop_background_tasks (before/after closing the bot)
if getattr(self, "_bot_task", None) and not self._bot_task.done():
    self._bot_task.cancel()
    try:
        await self._bot_task
    except asyncio.CancelledError:
        pass
🧰 Tools
🪛 Ruff (0.12.2)

54-56: Store a reference to the return value of asyncio.create_task

(RUF006)

🤖 Prompt for AI Agents
In backend/main.py around lines 54–56, the created discord bot task is discarded
which can hide startup exceptions and complicate shutdown; initialize
self._bot_task = None in __init__, assign the created task to self._bot_task
instead of discarding it, and in the shutdown/stop_background_tasks path check
if self._bot_task exists and is not done, call cancel() on it and await it
inside a try/except that ignores asyncio.CancelledError to ensure proper
cancellation and surfacing of other exceptions.

"aio-pika (>=9.5.5,<10.0.0)",
"uvicorn (>=0.35.0,<0.36.0)",
"ddgs (>=9.0.2,<10.0.0)",
"discord-py (>=2.5.2,<3.0.0)",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Do not add discord-py alongside py-cord; pick one Discord library.

  • py-cord (line 11) and discord.py share the same import namespace (import discord) and are intentionally incompatible; installing both leads to non-deterministic imports and runtime breakage. Pycord’s own docs instruct uninstalling discord.py before installing py-cord. Also, the official package name is discord.py, not discord-py. (guide.pycord.dev, pypi.org)

Apply one of the following:

Option A (keep Pycord; minimal change):

-    "discord-py (>=2.5.2,<3.0.0)",

Option B (migrate to official discord.py; remove Pycord too):

-    "py-cord (>=2.6.1,<3.0.0)",
+    "discord.py (>=2.6.0,<3.0.0)",

Then delete line 28 as in Option A.

Run to confirm there’s no mixed usage or stale pins:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Show discord deps declared
rg -nP 'discord\.py|py-cord|discord-py' pyproject.toml

# Find imports to verify single-namespace usage
rg -nP --type=py -C2 '^\s*(from\s+discord\b|import\s+discord\b)'

# Spot other requirement files that may still pin discord libs
fd -a -E node_modules -E .venv -E venv -E dist 'requirements*.txt' \
  | xargs -r -I{} rg -n 'discord\.py|py-cord|discord-py' {}

Length of output: 1263


Remove conflicting Discord libraries

  • Only install one Discord library—Pycord (py-cord) or the official discord.py—since both share the same import discord namespace and conflict.
    • Option A (keep Pycord): delete the "discord-py (>=2.5.2,<3.0.0)" entry (pyproject.toml line 28).
    • Option B (migrate to discord.py): in pyproject.toml replace the "py-cord (>=2.6.1,<3.0.0)" entry (line 11) with "discord.py (>=2.6.0,<3.0.0)" and remove the discord-py line.
  • Update any requirements.txt (e.g. the pin py-cord==2.6.1 at line 156) to install only your chosen library.
🤖 Prompt for AI Agents
In pyproject.toml around line 28 the project installs two conflicting Discord
libraries (py-cord and discord-py) that share the same import namespace; remove
the conflict by choosing one option: Option A (keep Pycord) — delete the
"discord-py (>=2.5.2,<3.0.0)" entry at line 28 and ensure any other references
use py-cord; Option B (migrate to discord.py) — replace the "py-cord
(>=2.6.1,<3.0.0)" entry with "discord.py (>=2.6.0,<3.0.0)" and remove the
discord-py line; then update requirements.txt (remove or change the pinned
py-cord==2.6.1 at line 156) so only the chosen library is installed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/App.tsx (1)

75-76: Nice: logout now calls Supabase signOut.

This addresses the prior review about terminating the session server-side.

🧹 Nitpick comments (3)
frontend/src/App.tsx (3)

24-24: Avoid any for repoData.

Consider introducing a RepoData type and passing it through props to improve safety.


29-36: Minor copy tweak for the error toast.

“User Login Failed” on a session fetch error can be misleading; suggest clarifying the message.

-        toast.error('User Login Failed');
+        toast.error('Session check failed');

47-51: Deduplicate “Signed out!” toast.

Both the SIGNED_OUT handler and handleLogout show a success toast, causing double notifications. Keep one (prefer the auth listener).

   const handleLogout = async () => {
     const { error } = await supabase.auth.signOut();
     if (error) {
       toast.error('Logout failed');
       console.error('Error during logout:', error);
       return;
     }
-    toast.success('Signed out!');
     setIsAuthenticated(false);
     setRepoData(null);
   };

Also applies to: 75-83

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1346cb and c43c775.

📒 Files selected for processing (1)
  • frontend/src/App.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/App.tsx (4)
frontend/src/lib/supabaseClient.ts (1)
  • supabase (12-12)
frontend/src/components/pages/ForgotPasswrdPage.tsx (1)
  • ForgotPasswrdPage (49-153)
frontend/src/components/pages/ResetPasswordPage.tsx (1)
  • ResetPasswordPage (50-201)
frontend/src/components/pages/SignUpPage.tsx (1)
  • SignUpPage (51-210)
🔇 Additional comments (4)
frontend/src/App.tsx (4)

3-5: LGTM: animation and toast wiring are appropriate.

Imports for AnimatePresence and react-hot-toast are correct and used properly below.


17-20: Auth pages and Supabase client imports look good.

Wiring aligns with the new flows (signup/forgot/reset).


27-27: No action needed.

Comment-only change.


123-139: Auth route gating looks correct.

  • Redirects authenticated users away from /login, /signup, and /forgot-password.
  • Leaves /reset-password public (appropriate for email links).

Comment on lines +38 to +39
const { data: subscription } = supabase.auth.onAuthStateChange(
(event, session) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify subscription handling and make cleanup safe.

Current cleanup may throw if subscription is undefined; also double “subscription.subscription” is awkward.

-    const { data: subscription } = supabase.auth.onAuthStateChange(
+    const { data: { subscription } } = supabase.auth.onAuthStateChange(
       (event, session) => {
         // ...
       }
     );
-    return () => {
-      subscription.subscription.unsubscribe();
-    };
+    return () => subscription.unsubscribe();

Also applies to: 66-68

🤖 Prompt for AI Agents
In frontend/src/App.tsx around lines 38-39 (and similarly 66-68), the code
assigns const { data: subscription } = supabase.auth.onAuthStateChange(...) then
later calls subscription.subscription which is awkward and the cleanup can throw
if subscription is undefined; change to capture the return value directly (e.g.,
const subscription = supabase.auth.onAuthStateChange(...)) and in the cleanup
use optional chaining or a null-check to call subscription.unsubscribe() only if
the subscription exists (or return a cleanup function that calls
subscription?.unsubscribe()). This removes the double
“subscription.subscription” and makes teardown safe.


const { data: subscription } = supabase.auth.onAuthStateChange(
(event, session) => {
console.log("Auth event:", event, session);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Do not log the full session object (risk of token leakage).

Access/refresh tokens can end up in browser logs. Log the event only (or at most session?.user.id).

-        console.log("Auth event:", event, session);
+        console.log("Auth event:", event);
📝 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
console.log("Auth event:", event, session);
console.log("Auth event:", event);
🤖 Prompt for AI Agents
In frontend/src/App.tsx around line 40, the current console.log prints the full
session object which may expose tokens; remove or replace that log so it does
not emit the session contents. Log only the event (e.g., console.log("Auth
event:", event)) or, if you need an identifier, log session?.user?.id or other
non-sensitive fields; ensure you do not include session.access_token,
session.refresh_token, or any token-like fields in logs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/components/landing/LandingPage.tsx (2)

16-19: Validate GitHub repo URLs, not just non-empty

Prevents avoidable backend calls and clearer UX.

-    if (!repoUrl) {
-      toast.error('Please enter a valid GitHub repository URL.');
-      return;
-    }
+    if (!/^https?:\/\/github\.com\/[^\/\s]+\/[^\/\s]+\/?$/.test(repoUrl)) {
+      toast.error('Please enter a valid GitHub repository URL (owner/repo).');
+      return;
+    }

1-6: Don’t hard-code localhost; add Supabase auth header

Use an env-configured API base to avoid mixed content in prod and include the Supabase access token if the backend protects this endpoint.

 import React, { useState } from 'react';
 import { motion } from 'framer-motion';
 import axios from 'axios';
 import { toast } from 'react-hot-toast';
 import { useNavigate } from 'react-router-dom';
+import { supabase } from '../../lib/supabaseClient';
🧹 Nitpick comments (2)
frontend/src/components/landing/LandingPage.tsx (2)

7-9: Avoid any for repo data

Type the payload to keep call-sites safe and future-proof.

-interface Props {
-  setRepoData: (data: any) => void; // Function to pass data to parent
-}
+type RepoStats = Record<string, unknown>; // TODO: refine shape
+interface Props {
+  setRepoData: (data: RepoStats) => void;
+}

41-58: Submit on Enter and add basic a11y

Wrap controls in a form to allow Enter key submission, and add small a11y niceties.

-      <div className="bg-gray-900 p-8 rounded-lg shadow-lg w-full max-w-md">
+      <div className="bg-gray-900 p-8 rounded-lg shadow-lg w-full max-w-md">
         <h1 className="text-3xl font-bold mb-4">Welcome to Devr.AI</h1>
         <p className="text-gray-400 mb-6">Enter a GitHub repository URL to analyze its stats.</p>
-        <input
-          type="text"
-          placeholder="https://github.com/owner/repo"
-          value={repoUrl}
-          onChange={(e) => setRepoUrl(e.target.value)}
-          className="w-full p-3 rounded-lg bg-gray-800 text-white mb-4"
-        />
-        <button
-          onClick={fetchRepoStats}
-          disabled={loading}
-          className={`w-full py-3 rounded-lg text-white ${loading ? 'bg-gray-700' : 'bg-green-500 hover:bg-green-600'} transition-colors`}
-        >
-          {loading ? 'Fetching...' : 'Analyze Repository'}
-        </button>
+        <form onSubmit={(e) => { e.preventDefault(); fetchRepoStats(); }}>
+          <input
+            type="url"
+            placeholder="https://github.com/owner/repo"
+            value={repoUrl}
+            onChange={(e) => setRepoUrl(e.target.value)}
+            className="w-full p-3 rounded-lg bg-gray-800 text-white mb-4"
+            aria-label="GitHub repository URL"
+            autoFocus
+            autoComplete="off"
+            inputMode="url"
+          />
+          <button
+            type="submit"
+            disabled={loading}
+            aria-busy={loading}
+            className={`w-full py-3 rounded-lg text-white ${loading ? 'bg-gray-700' : 'bg-green-500 hover:bg-green-600'} transition-colors`}
+          >
+            {loading ? 'Fetching...' : 'Analyze Repository'}
+          </button>
+        </form>
       </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c43c775 and fbe610a.

📒 Files selected for processing (2)
  • frontend/src/components/landing/LandingPage.tsx (2 hunks)
  • frontend/src/components/pages/ProfilePage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/pages/ProfilePage.tsx
🔇 Additional comments (4)
frontend/src/components/landing/LandingPage.tsx (4)

5-5: Router-based navigation import looks good

Switching to useNavigate aligns this page with the new auth + router flow.


14-14: Inject API base URL via env
Use Vite’s import.meta.env to inject VITE_API_BASE_URL in LandingPage.tsx:

   const navigate = useNavigate();
+  const API_BASE = import.meta.env.VITE_API_BASE_URL || '';

11-14: LandingPage prop API change verified
No remaining call-sites pass the deprecated setActivePage prop; LandingPage is correctly invoked with setRepoData, and the only setActivePage occurrence in BotIntegrationPage.tsx is a local state setter.


26-26: ProtectedLayout secures /dashboard
The /dashboard route is nested under the root <Route path="/" element={isAuthenticated ? <ProtectedLayout/> : <Navigate to='/login'/>}>, so unauthenticated users are redirected to /login and cannot access dashboard content.

Comment on lines 23 to 29
const response = await axios.post('http://localhost:8000/api/repo-stats', { repo_url: repoUrl });
setRepoData(response.data); // Pass fetched data to parent
toast.success('Repository stats fetched successfully!');
setActivePage('dashboard'); // Navigate to dashboard
navigate('/dashboard'); // Navigate to dashboard
} catch (error) {
toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
} finally {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Pass auth token, add timeout, and route 401s to login

Improves security, resilience, and UX.

-      const response = await axios.post('http://localhost:8000/api/repo-stats', { repo_url: repoUrl });
+      const { data: { session } } = await supabase.auth.getSession();
+      const response = await axios.post(
+        `${API_BASE}/api/repo-stats`,
+        { repo_url: repoUrl },
+        {
+          headers: session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : undefined,
+          timeout: 15000,
+        }
+      );
       setRepoData(response.data); // Pass fetched data to parent
       toast.success('Repository stats fetched successfully!');
       navigate('/dashboard'); // Navigate to dashboard
-    } catch (error) {
-      toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        if (error.response?.status === 401) {
+          toast.error('Please log in to analyze repositories.');
+          navigate('/login');
+          return;
+        }
+        const msg = error.response?.data?.message ?? error.message;
+        toast.error(`Failed to fetch repository stats: ${msg}`);
+      } else {
+        toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
+      }
     } finally {
       setLoading(false);
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/components/landing/LandingPage.tsx around lines 23 to 29, the
axios POST call should include the user's auth token, a request timeout, and
explicit handling of 401 responses; change the request to send headers: {
Authorization: `Bearer ${token}` } (obtain token from localStorage or auth
context) and add a timeout option (e.g., 10000 ms), then in the catch block
inspect the error response status: if 401 navigate('/login') and show an
appropriate toast, otherwise show the existing generic failure toast; keep
finally behavior unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEATURE REQUEST:Adding an Authentication System

3 participants