Skip to content

feat: enhance Google authentication flow and user profile handling in…#19

Open
Prolomon wants to merge 1 commit intomainfrom
debug
Open

feat: enhance Google authentication flow and user profile handling in…#19
Prolomon wants to merge 1 commit intomainfrom
debug

Conversation

@Prolomon
Copy link
Copy Markdown
Collaborator

Authentication Flow Improvements

  • Changed the backend call for Google OAuth in googleAuth from a POST to a GET request, matching the updated backend implementation (src/lib/api/auth.api.ts).
  • Improved error handling in the Google callback page by checking for multiple possible error message formats and providing more robust error messages (src/app/auth/google-callback/page.tsx).

User Profile and Avatar Handling

  • Added a comprehensive UserProfile interface and now use the authenticated user's profile (including avatar) for displaying the user icon in both the dashboard and header-only layouts. If an avatar exists, it is shown; otherwise, the username/email initial is displayed

Debug Logging

  • Added debug logs throughout the Google authentication flow to log important parameters and responses, including Google callback parameters, login responses, and client configuration (src/app/auth/google-callback/page.tsx, src/hooks/auth/useAuth.ts).

Other Minor Updates

  • Updated the default Google client ID reference to only use the environment variable, removing the hardcoded fallback in useAuth.

These changes collectively improve the reliability, maintainability, and debuggability of the authentication and user profile display features.

Copilot AI review requested due to automatic review settings April 16, 2026 00:08
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperbuds-app Ready Ready Preview, Comment Apr 16, 2026 0:08am

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Google OAuth client flow to match backend changes, improves callback error handling, and starts using authenticated profile data (incl. avatar) for user icons across layouts.

Changes:

  • Switch Google OAuth backend call from POST to GET and enhance callback error-message extraction.
  • Use authenticated user profile data to render avatars/initials in dashboard + header-only layouts.
  • Add additional debug logging across the Google auth flow and remove hardcoded Google client ID fallback.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/lib/api/endpoints.ts Removes commented-out alternate base URL configuration.
src/lib/api/auth.api.ts Updates googleAuth to use GET with query string.
src/hooks/auth/useAuth.ts Adds debug logs, removes hardcoded Google client ID fallback, minor whitespace changes.
src/config/baseUrl.ts Removes commented-out alternate base URL configuration.
src/components/layout/HeaderOnly/HeaderOnly.tsx Adds profile-based avatar rendering (and a large inline profile type).
src/components/layout/Dashboard/Dashboard.tsx Adds profile-based avatar rendering for mobile header (and a large inline profile type).
src/app/auth/google-callback/page.tsx Adds debug logs and expands error-message extraction logic.
Comments suppressed due to low confidence (2)

src/app/auth/google-callback/page.tsx:35

  • processingRef.current is only set to true after the code check. In React Strict Mode (and on rerenders triggered by setStatus), the effect can re-run in the error / !code paths and schedule multiple timeouts / redirects. Set processingRef.current = true before the early-return branches (or immediately when the effect starts) to ensure the callback handler runs at most once.
  useEffect(() => {
    if (processingRef.current) return;
    
    const handleCallback = async () => {
      const code = searchParams.get("code");
      const error = searchParams.get("error");

      console.log("Google Callback Params - Code:", code, "Error:", error); // Debug log

      if (error) {
        setStatus(`❌ Google authentication failed: ${error}`);
        setTimeout(() => router.push("/auth/signin?error=google_auth_failed"), 2000);
        return;
      }

      if (!code) {
        setStatus("❌ No authorization code received from Google");
        setTimeout(() => router.push("/auth/signin?error=no_code"), 2000);
        return;
      }

src/hooks/auth/useAuth.ts:152

  • clientId can be undefined when NEXT_PUBLIC_GOOGLE_CLIENT_ID is not configured, producing an invalid Google OAuth URL and a confusing failure mode. Add a guard that throws/sets a user-facing error when clientId is missing (and consider skipping the debug log in that case).
    const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
    const baseUrl = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
    const redirectUri = process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI || `${baseUrl}/auth/google-callback`;
    console.log("Initiating Google Login - Client ID:", clientId, "Redirect URI:", redirectUri); // Debug log
    const scope = "email profile";
    const responseType = "code";

    const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=${responseType}&scope=${scope}&access_type=offline&prompt=consent`;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +152
interface UserProfile {
stats: {
followers: number;
engagementRate: number;
activityLevel: number;
};
preferences: {
engagementRange: {
min: number;
max: number;
};
minRizzScore: number;
preferredNiches: string[];
collabType: string[];
locationPreference: string;
};
subscription: {
tier: string;
cancelAtPeriodEnd: boolean;
};
paymentPreferences: {
currency: string;
autoPayoutEnabled: boolean;
autoPayoutThreshold: number;
payoutSchedule: string;
};
earnings: {
totalEarned: number;
availableBalance: number;
pendingPayouts: number;
};
accountStatus: {
payoutsEnabled: boolean;
paymentMethodsEnabled: boolean;
accountRestricted: boolean;
};
notificationPreferences: {
paymentReceived: boolean;
payoutProcessed: boolean;
subscriptionChanges: boolean;
paymentFailed: boolean;
};
_id: string;
email: string;
isVerified: boolean;
role: string;
niche: string[];
rizzScore: number;
createdAt: string;
updatedAt: string;
__v: number;
subscriptionStatus: string;
formattedEarnings: {
totalEarned: string;
availableBalance: string;
pendingPayouts: string;
};
id: string;
profile: {
stats: {
platformBreakdown: {
tiktok: {
followers: number;
engagement: number;
};
instagram: {
followers: number;
engagement: number;
};
youtube: {
followers: number;
engagement: number;
};
twitch: {
followers: number;
engagement: number;
};
};
totalFollowers: number;
avgEngagement: number;
};
preferences: {
audienceSize: {
min: number;
max: number;
};
budget: {
min: number;
max: number;
};
collaborationTypes: string[];
niches: string[];
minRizzScore: number;
maxDistance: number;
locations: string[];
};
_id: string;
userId: string;
username: string;
displayName: string;
bio: string;
avatar: string;
niche: string[];
rizzScore: number;
isPublic: boolean;
isActive: boolean;
location: {
type: string;
coordinates: [number, number];
country: string;
city: string;
state: string;
};
lastSeen: string;
createdAt: string;
updatedAt: string;
__v: number;
profileUrl: string;
id: string;
};
}

// Mobile Header Buttons Component
function MobileHeaderButtons({ user }: { user: User | null }) {
const { isDarkMode, toggleDarkMode } = useTheme();
const [profile, setProfile] = useState<UserProfile | null>(null);
const { user: authenticatedUser } = useAuth();

useEffect(() => {
if (authenticatedUser) {
setProfile(authenticatedUser as UserProfile);
}
}, [authenticatedUser])

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This file adds another inline copy of the huge UserProfile interface. Since the same shape is already duplicated in other layout components, please extract a shared type (e.g. src/types/user.types.ts) and import it to avoid drift and repeated as UserProfile casts.

Copilot uses AI. Check for mistakes.
@@ -72,7 +204,11 @@ export default function HeaderOnlyLayout({ children }: HeaderOnlyLayoutProps) {
<div className="flex gap-2 items-center">
<div className="flex justify-center items-center w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full">
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The avatar container is missing overflow-hidden, so the rendered <img> can bleed outside the rounded circle when the source image isn’t perfectly square. Other layouts include overflow-hidden; add it here for consistent, correct clipping.

Suggested change
<div className="flex justify-center items-center w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full">
<div className="flex overflow-hidden justify-center items-center w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full">

Copilot uses AI. Check for mistakes.
Comment thread src/lib/api/auth.api.ts
*/
export async function googleAuth(code: string): Promise<GoogleAuthResponse> {
const response = await apiClient.post<GoogleAuthResponse>("/auth/google", { code });
const response = await apiClient.get<GoogleAuthResponse>("/auth/google?code=" + code);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

googleAuth builds the URL by string-concatenating the authorization code. This can break when the code contains characters that must be URL-encoded and makes it easy to introduce subtle query-string bugs. Prefer passing code via Axios params (or at least encodeURIComponent(code)) so the request is encoded correctly and consistent with other API calls in the repo.

Suggested change
const response = await apiClient.get<GoogleAuthResponse>("/auth/google?code=" + code);
const response = await apiClient.get<GoogleAuthResponse>("/auth/google", {
params: { code },
});

Copilot uses AI. Check for mistakes.
Comment thread src/hooks/auth/useAuth.ts
Comment on lines 30 to 38
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
credentials: 'include',
credentials: 'include',
})

const data = await res.json()
if (!res.ok) {
dispatch(setLoading(false))
dispatch(setLoading(false))
throw new Error(data.message || 'Login failed')
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

There are trailing spaces on these lines (e.g. after the comma / closing paren). If Prettier/ESLint is enforced, this can cause avoidable formatting-only diffs or lint failures—please remove the trailing whitespace.

Copilot uses AI. Check for mistakes.
Comment thread src/hooks/auth/useAuth.ts

try {
const data = await googleAuth(code)
console.log("Google Login Data: ", data) // Debug log
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

console.log("Google Login Data", data) will log the full Google auth response, which typically includes the access token and user details. Logging credentials/PII in the browser console is a security risk; please remove this or gate it behind an explicit debug flag that is disabled in production.

Suggested change
console.log("Google Login Data: ", data) // Debug log

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +210
{profile?.profile?.avatar ? (
<Image src={profile?.profile?.avatar} alt="User avatar" width={32} height={32} className="w-full h-full object-cover" />
) : (
profile?.profile?.username?.[0]?.toUpperCase() || profile?.email[0].toUpperCase()
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

If authenticatedUser is null (e.g. after a hard refresh where only fetchUser succeeds), profile stays null and the avatar/initial renders as empty. Also, profile?.email[0].toUpperCase() can throw if email is undefined/empty because optional chaining doesn't cover the index access. Consider falling back to the already-fetched user state and using profile?.email?.[0]?.toUpperCase() (same for username) to avoid runtime errors.

Suggested change
{profile?.profile?.avatar ? (
<Image src={profile?.profile?.avatar} alt="User avatar" width={32} height={32} className="w-full h-full object-cover" />
) : (
profile?.profile?.username?.[0]?.toUpperCase() || profile?.email[0].toUpperCase()
{(profile ?? user)?.profile?.avatar ? (
<Image src={(profile ?? user)?.profile?.avatar} alt="User avatar" width={32} height={32} className="w-full h-full object-cover" />
) : (
(profile ?? user)?.profile?.username?.[0]?.toUpperCase() || (profile ?? user)?.email?.[0]?.toUpperCase() || ''

Copilot uses AI. Check for mistakes.
Comment on lines +177 to +180
{profile?.profile?.avatar ? (
<Image src={profile?.profile?.avatar} alt="User avatar" width={32} height={32} className="w-full h-full object-cover" />
) : (
profile?.profile?.username?.[0]?.toUpperCase() || profile?.email[0].toUpperCase()
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

MobileHeaderButtons ignores its user prop for the fallback initial and relies only on profile from useAuth. During initial render (or if Redux user isn’t hydrated yet), this can render an empty avatar. Use user as a fallback (and guard index access with ?.[0]?) so the icon always renders.

Suggested change
{profile?.profile?.avatar ? (
<Image src={profile?.profile?.avatar} alt="User avatar" width={32} height={32} className="w-full h-full object-cover" />
) : (
profile?.profile?.username?.[0]?.toUpperCase() || profile?.email[0].toUpperCase()
{profile?.profile?.avatar || user?.avatar ? (
<Image
src={profile?.profile?.avatar || user?.avatar || ''}
alt="User avatar"
width={32}
height={32}
className="w-full h-full object-cover"
/>
) : (
profile?.profile?.username?.[0]?.toUpperCase() ||
user?.username?.[0]?.toUpperCase() ||
profile?.email?.[0]?.toUpperCase() ||
user?.email?.[0]?.toUpperCase()

Copilot uses AI. Check for mistakes.
Comment on lines 20 to +46
@@ -40,6 +42,8 @@ export default function GoogleCallbackPage() {
// ✅ This calls the backend AND updates Redux + LocalStorage
const data = await googleLogin(code);

console.log("Google Login Response Data: ", data); // Debug log

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The Google callback page logs the OAuth code and the full login response to the console. The code and resulting access token are sensitive and can be captured in logs/screenshots; please remove these logs or gate them behind a non-production debug flag that redacts tokens.

Copilot uses AI. Check for mistakes.
Comment on lines 140 to +151
export default function HeaderOnlyLayout({ children }: HeaderOnlyLayoutProps) {
const router = useRouter();
const [user, setUser] = useState<{ id: string; name: string; email: string; avatar?: string } | null>(null);
const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState<UserProfile | null>(null);
const { user: authenticatedUser } = useAuth();

useEffect(() => {
if (authenticatedUser) {
setProfile(authenticatedUser as UserProfile);
}
}, [authenticatedUser])
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This component defines a very large UserProfile interface inline and then uses authenticatedUser as UserProfile. The cast defeats type-safety and the duplicated interface will be hard to keep consistent across layouts. Move this type to a shared location (e.g. src/types/user.types.ts) and have useAuth/Redux user types reference it instead of casting.

Suggested change
export default function HeaderOnlyLayout({ children }: HeaderOnlyLayoutProps) {
const router = useRouter();
const [user, setUser] = useState<{ id: string; name: string; email: string; avatar?: string } | null>(null);
const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState<UserProfile | null>(null);
const { user: authenticatedUser } = useAuth();
useEffect(() => {
if (authenticatedUser) {
setProfile(authenticatedUser as UserProfile);
}
}, [authenticatedUser])
type AuthenticatedUserProfile = ReturnType<typeof useAuth>['user'];
export default function HeaderOnlyLayout({ children }: HeaderOnlyLayoutProps) {
const router = useRouter();
const [user, setUser] = useState<{ id: string; name: string; email: string; avatar?: string } | null>(null);
const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState<AuthenticatedUserProfile>(null);
const { user: authenticatedUser } = useAuth();
useEffect(() => {
setProfile(authenticatedUser);
}, [authenticatedUser]);

Copilot uses AI. Check for mistakes.
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.

2 participants