Skip to content

Commit

Permalink
Show UserButton when user is logged in (#240)
Browse files Browse the repository at this point in the history
* fix: show userbutton when user is logged in

* Merge main manually

---------

Co-authored-by: Ricky Zhang <[email protected]>
  • Loading branch information
armans-code and TheRickyZhang authored Feb 25, 2025
1 parent db8e2ec commit 4e94d94
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"files.associations": {
"*.css": "tailwindcss"
}
}
42 changes: 36 additions & 6 deletions src/client/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
// AuthContext.js
import React, { createContext, useContext, useState } from "react";
import React, { createContext, useContext, useEffect, useState } from "react";
import type { ReactNode } from "react";

export interface AuthContextType {
isAuthenticated: boolean;
login: () => void;
logout: () => void;
isLoading: boolean;
}

// Create AuthContext as undefined values for now:
// const AuthContext = createContext<AuthContextType | undefined>(undefined);

// Create the AuthContext with default values
const AuthContext = createContext({
const AuthContext = createContext<AuthContextType>({
isAuthenticated: false,
login: () => {},
logout: () => {},
isLoading: true,
});

interface AuthProviderProps {
Expand All @@ -24,12 +26,40 @@ interface AuthProviderProps {
// AuthProvider component that wraps your app and provides auth state
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState(true);

// TODO: Mock funcitons!
const login = () => setIsAuthenticated(true);
const logout = () => setIsAuthenticated(false);
const checkSession = async () => {
try {
const response = await fetch("/api/auth/session", {
credentials: "include",
});

return <AuthContext.Provider value={{ isAuthenticated, login, logout }}>{children}</AuthContext.Provider>;
if (response.ok) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
} catch (error) {
console.error(error);
setIsAuthenticated(false);
} finally {
setIsLoading(false); // userbutton wont render unless loading is false
}
};

useEffect(() => {
checkSession(); // validate user session whenever authcontext renders
}, []);

const login = () => {
setIsAuthenticated(true);
};

const logout = () => {
setIsAuthenticated(false);
};

return <AuthContext.Provider value={{ isAuthenticated, login, logout, isLoading }}>{children}</AuthContext.Provider>;
};

// Custom hook to use the AuthContext
Expand Down
5 changes: 3 additions & 2 deletions src/client/components/navigation/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const Header: React.FC = () => {
const location = useLocation();
const isHomePage = location.pathname === "/";
const [menuOpen, setMenuOpen] = useState(false);
const { isAuthenticated, logout } = useAuth();
const { isAuthenticated, isLoading, logout } = useAuth();
const menuRef = useRef<HTMLDivElement>(null);
const hamburgerRef = useRef<HTMLButtonElement>(null);

Expand Down Expand Up @@ -98,14 +98,15 @@ const Header: React.FC = () => {
<DesktopMenu navItems={navItems} isHomePage={isHomePage} />
<SearchBar />
<div className="hidden md:block">
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} isHomePage={isHomePage} />
{!isLoading && <UserButton isLoggedIn={isAuthenticated} onLogout={logout} isHomePage={isHomePage} />}
</div>
</div>
</div>

{/* Mobile Nav */}
<div className="flex items-center gap-4 md:hidden">
<SearchBar className="w-32 focus:w-64" />
{!isLoading && <UserButton isLoggedIn={isAuthenticated} onLogout={logout} isHomePage={isHomePage} />}
<button ref={hamburgerRef} className="focus:outline-none">
<Hamburger toggled={menuOpen} toggle={setMenuOpen} color={isHomePage ? "#fff" : "#000"} size={22} />
</button>
Expand Down
37 changes: 37 additions & 0 deletions src/server/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,43 @@ authRoutes.post("/auth/logout", async (c) => {
}
});

// used for validating sessions
authRoutes.get("/auth/session", async (c) => {
const sessionId = c.req.header("Cookie")?.match(/sessionId=([^;]*)/)?.[1];

if (!sessionId) {
return new Response("No active session", { status: 401 });
}

try {
const session = await db.select().from(Schema.sessions).where(eq(Schema.sessions.id, sessionId));

if (session.length === 0) {
return new Response("Session not found", { status: 401 });
}

if (session[0].expires_at < Date.now()) {
await db.delete(Schema.sessions).where(eq(Schema.sessions.id, sessionId));
// maybe renew session?
return new Response("Session expired", { status: 401 });
}

const user = await db.select({ username: Schema.users.username }).from(Schema.users).where(eq(Schema.users.id, session[0].user_id));

if (user.length === 0) {
return new Response("User not found", { status: 401 });
}

return new Response(JSON.stringify({ username: user[0].username }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.log(error);
return new Response("Error checking session", { status: 500 });
}
});

async function createSession(sessionID: string, userID: string) {
try {
await db.insert(Schema.sessions).values({
Expand Down

0 comments on commit 4e94d94

Please sign in to comment.