Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { appRouter } from "./routers/index";

const baseCorsConfig = {
origin: env.CORS_ORIGIN,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
credentials: true,
maxAge: 86400,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/routers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export async function authRoutes(fastify: FastifyInstance) {

if (newUserResult.length > 0) {
userId = newUserResult[0].id;
redirectPath = `${env.FRONTEND_URL}/user-details`;
redirectPath = `${env.FRONTEND_URL}/settings/profile`;
isNewUser = true;
fastify.log.info("New user created", { userId, email });
} else {
Expand Down
136 changes: 90 additions & 46 deletions apps/web/src/components/mode-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,97 @@
import { Moon, Sun } from "lucide-react";
import { useTheme } from "@/components/theme-provider";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
"use client";

import { Droplet } from "lucide-react";
import type React from "react";
import { useEffect, useState } from "react";
import { type Theme, useTheme } from "@/components/theme-provider";

// Explicitly type the array to match the Theme type
const themes: {
name: Theme;
label: string;
icon: React.ElementType;
color: string;
}[] = [
{ name: "blue", label: "Blue", icon: Droplet, color: "#2563eb" },
{ name: "green", label: "Green", icon: Droplet, color: "#16a34a" },
{ name: "purple", label: "Purple", icon: Droplet, color: "#9333ea" },
{ name: "orange", label: "Orange", icon: Droplet, color: "#ea580c" },
{ name: "teal", label: "Teal", icon: Droplet, color: "#14b8a6" },
];

export function ModeToggle() {
const { setTheme } = useTheme();
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return (
<div className="border-3 border-border bg-card px-4 py-2 font-bold shadow-[4px_4px_0px_0px_var(--shadow-color)]">
<Droplet className="h-5 w-5" />
</div>
);
}

const currentTheme = themes.find((t) => t.name === theme) || themes[0];
const Icon = currentTheme.icon;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="neo-brutal-button bg-[var(--button-red)] border-black hover:opacity-90 focus-visible:ring-0 focus-visible:border-black shadow-[4px_4px_0_0_black] active:shadow-[2px_2px_0_0_black] active:translate-x-[2px] active:translate-y-[2px]"
style={{ color: "black" }}
>
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="bg-white border-4 border-black shadow-lg m-2"
<div className="relative">
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="border-3 flex items-center gap-2 border-border bg-card px-4 py-2 font-bold text-card-foreground shadow-[4px_4px_0px_0px_var(--shadow-color)] transition-all hover:shadow-[2px_2px_0px_0px_var(--shadow-color)] hover:translate-x-[2px] hover:translate-y-[2px]"
>
<DropdownMenuItem
className="mx-4 text-black font-bold"
onClick={() => setTheme("light")}
>
Light
</DropdownMenuItem>
<DropdownMenuItem
className="mx-4 text-black font-bold"
onClick={() => setTheme("dark")}
>
Dark
</DropdownMenuItem>
<DropdownMenuItem
className="mx-4 text-black font-bold"
onClick={() => setTheme("system")}
>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Icon className="h-5 w-5" style={{ color: currentTheme.color }} />
<span className="hidden sm:inline">{currentTheme.label}</span>
</button>

{isOpen && (
<>
<div
className="fixed inset-0 z-10"
role="button"
tabIndex={0}
onClick={() => setIsOpen(false)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " " || e.key === "Escape")
setIsOpen(false);
}}
/>
Comment on lines +55 to +64

Choose a reason for hiding this comment

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

medium

For accessibility, the overlay div used to close the theme selector should have an aria-label to describe its purpose to screen reader users. Since it has a role="button", an accessible name is expected.

					<div
						className="fixed inset-0 z-10"
						role="button"
						tabIndex={0}
						aria-label="Close theme selector"
						onClick={() => setIsOpen(false)}
						onKeyDown={(e) => {
							if (e.key === "Enter" || e.key === " " || e.key === "Escape")
								setIsOpen(false);
						}}
					/>

<div className="absolute right-0 top-full z-20 mt-2 w-48 border-4 border-border bg-card shadow-[8px_8px_0px_0px_var(--shadow-color)]">
<div className="p-2 space-y-1">
{themes.map((themeOption) => {
const ThemeIcon = themeOption.icon;
return (
<button
type="button"
key={themeOption.name}
onClick={() => {
setTheme(themeOption.name);
setIsOpen(false);
}}
className={`flex w-full items-center gap-3 border-2 border-border px-4 py-2 font-bold transition-all hover:translate-x-[2px] hover:translate-y-[2px] ${
theme === themeOption.name
? "bg-primary text-primary-foreground"
: "bg-card text-card-foreground hover:bg-muted"
}`}
>
<ThemeIcon
className="h-4 w-4"
style={{ color: themeOption.color }}
/>
{themeOption.label}
</button>
);
})}
</div>
</div>
</>
)}
</div>
);
}
19 changes: 4 additions & 15 deletions apps/web/src/components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext, useContext, useEffect, useState } from "react";

type Theme = "dark" | "light" | "system";
export type Theme = "blue" | "green" | "purple" | "orange" | "teal";

type ThemeProviderProps = {
children: React.ReactNode;
Expand All @@ -14,15 +14,15 @@ type ThemeProviderState = {
};

const initialState: ThemeProviderState = {
theme: "system",
theme: "blue",
setTheme: () => null,
};

const ThemeProviderContext = createContext<ThemeProviderState>(initialState);

export function ThemeProvider({
children,
defaultTheme = "system",
defaultTheme = "blue",
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
Expand All @@ -32,18 +32,7 @@ export function ThemeProvider({

useEffect(() => {
const root = window.document.documentElement;

root.classList.remove("light", "dark");

if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";

root.classList.add(systemTheme);
return;
}
root.classList.remove("blue", "green", "purple", "orange", "teal");

Choose a reason for hiding this comment

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

medium

The list of theme class names to remove is hardcoded. This can lead to maintainability issues if themes are added or removed. It's better to have a single source of truth for the theme names and derive this list from it. You could create a shared file that exports the theme configurations, which would be used here and in mode-toggle.tsx.


root.classList.add(theme);
}, [theme]);
Expand Down
14 changes: 9 additions & 5 deletions apps/web/src/components/ui/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const Footer = () => {
<div className="w-8 h-8 bg-secondary border-2 border-primary pixel-pulse"></div>
<h3 className="pixel-font text-xl text-primary">IIITBuzz</h3>
</div>
<p className="text-sm text-muted mb-4">
<p className="text-sm text-muted-foreground mb-4">
The ultimate community platform for IIIT students. Connect, learn,
and grow together in our vibrant digital campus ecosystem.
</p>
Expand Down Expand Up @@ -62,7 +62,7 @@ const Footer = () => {
<li key={link}>
<a
href={`#${link.toLowerCase().replace(/ & | /g, "-")}`}
className="text-muted hover:text-primary transition-colors"
className="text-muted-foreground hover:text-primary transition-colors"
>
{link}
</a>
Expand All @@ -84,7 +84,7 @@ const Footer = () => {
<li key={policy}>
<a
href={`#${policy.toLowerCase().replace(/ /g, "-")}`}
className="text-muted hover:text-primary transition-colors"
className="text-muted-foreground hover:text-primary transition-colors"
>
{policy}
</a>
Expand All @@ -95,8 +95,12 @@ const Footer = () => {
</div>

<div className="border-t-2 border-muted pt-8 text-center">
<p className="pixel-font text-xs text-muted m-4">© 2025 IIITBuzz</p>
<p className="text-xs text-muted mt-2">by- P-Soc IIIT-bh</p>
<p className="pixel-font text-xs text-muted-foreground m-4">
© 2025 IIITBuzz
</p>
<p className="text-xs text-muted-foreground mt-2">
by- P-Soc IIIT-bh
</p>
</div>
</div>
</footer>
Expand Down
22 changes: 10 additions & 12 deletions apps/web/src/components/ui/header.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Link } from "react-router";
import { ModeToggle } from "@/components/mode-toggle";
import { Button } from "@/components/ui/button";
import { useAuth } from "@/contexts/AuthContext";
import { ModeToggle } from "../mode-toggle";

const Header = () => {
interface HeaderProps {
hideThemeToggle?: boolean;
}

const Header = ({ hideThemeToggle = false }: HeaderProps) => {
const { user, isLoading, isAuthenticated, login, logout } = useAuth();

return (
Expand All @@ -15,24 +19,19 @@ const Header = () => {
</Link>

<div className="flex items-center space-x-3">
<ModeToggle />
{!hideThemeToggle && <ModeToggle />}

{isLoading ? (
// Show loading state
<div className="animate-pulse text-sm text-muted-foreground">
Loading...
</div>
) : isAuthenticated ? (
// Show authenticated user options
<>
<span className="text-sm text-foreground">
<span className="text-sm text-foreground hidden md:inline">
Welcome, {user?.firstName || user?.username || user?.email}!
</span>
<Link
to={
user?.username ? `/profile/${user.username}` : "/my/profile"
}
>

<Link to={`/profile/${user?.username}`}>

Choose a reason for hiding this comment

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

high

The link to the user's profile page assumes user.username is always available. If a user hasn't set their username yet, user.username can be null, which would create a broken link to /profile/null. The previous implementation had a fallback. A better approach would be to link to the profile settings page if the username is not set.

Suggested change
<Link to={`/profile/${user?.username}`}>
<Link to={user?.username ? `/profile/${user.username}` : "/settings/profile"}>

<Button
variant="outline"
className="neo-brutal-button border-primary text-primary bg-secondary hover:bg-secondary hover:text-black"
Expand All @@ -48,7 +47,6 @@ const Header = () => {
</Button>
</>
) : (
// Show unauthenticated user options (current state)
<>
<Button
onClick={login}
Expand Down
Loading