Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
511 changes: 511 additions & 0 deletions app/protected/jobs/page.tsx

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions app/protected/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ const sidebarItems: SidebarGroupType[] = [
url: "/protected/projects",
icon: Briefcase,
},
{
title: "Jobs",
url: "/protected/jobs",
icon: Briefcase,
},
Comment on lines +111 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Duplicate “Jobs” link (also under Career) — de-duplicate to avoid confusion.

You now have two items pointing to /protected/jobs (“Jobs” here and “Job Opportunities” under Career). Keep one to maintain IA clarity.

Apply one of:

-      {
-        title: "Jobs",
-        url: "/protected/jobs",
-        icon: Briefcase,
-      },

or rename/retarget as needed.

📝 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
{
title: "Jobs",
url: "/protected/jobs",
icon: Briefcase,
},
🤖 Prompt for AI Agents
In app/protected/layout.tsx around lines 91-95 there is a duplicate navigation
item pointing to /protected/jobs ("Jobs") which also exists as "Job
Opportunities" under Career; remove this duplicate entry (delete the object at
those lines) to keep a single link, or if you intended a different destination,
rename the title and update the url/icon to the correct route so the two entries
are distinct.

{
title: "Achievements",
url: "/protected/achievements",
Expand Down
14 changes: 7 additions & 7 deletions app/projects/page.tsx → app/zenith-hall/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,18 @@ export default function ProjectsPage() {
<span className="absolute inset-0 rounded-full bg-[image:radial-gradient(75%_100%_at_50%_0%,rgba(56,189,248,0.6)_0%,rgba(56,189,248,0)_75%)] opacity-0 group-hover:opacity-100" />
</span>
<div className="relative flex space-x-2 items-center z-10 rounded-full bg-zinc-950 py-0.5 px-4 ring-1 ring-white/10">
<span>Projects Showcase</span>
<span>Hall of Excellence</span>
<Sparkles className="w-3 h-3" />
</div>
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-emerald-400/90 to-emerald-400/0 group-hover:opacity-40" />
</button>
</div>
</div>
<h1 className="text-5xl md:text-6xl font-bold tracking-tight leading-tight">
Intern <span className="gradient-text">Projects</span> Showcase
<span className="gradient-text">Zenith</span> Hall
</h1>
<p className="text-xl md:text-2xl text-muted-foreground max-w-3xl mx-auto leading-relaxed">
Explore outstanding projects created by our talented interns during their internship at Codeunia.
<p className="text-xl md:text-2xl text-muted-foreground max-w-4xl mx-auto leading-relaxed">
A tribute to Codeunia&apos;s finest minds who have risen above challenges with exceptional skill and innovation. Here, we showcase our best members whose remarkable projects and achievements reflect technical excellence, creative problem-solving, and the spirit of collaboration that defines our community.
</p>
</div>
</div>
Expand All @@ -121,7 +121,7 @@ export default function ProjectsPage() {
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input
type="text"
placeholder="Search projects..."
placeholder="Search our hall of excellence by project, technology, or member..."
className="w-full pl-12 pr-4 py-3 text-lg bg-background/70 backdrop-blur-sm rounded-full focus:ring-2 focus:ring-primary/50"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
Expand All @@ -130,15 +130,15 @@ export default function ProjectsPage() {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="flex items-center gap-2 rounded-full text-lg py-3 bg-background/70 backdrop-blur-sm">
<span>Tags</span>
<span>Technologies</span>
{selectedTags.length > 0 && (
<Badge variant="secondary" className="rounded-full">{selectedTags.length}</Badge>
)}
<ChevronDown className="h-5 w-5 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-64">
<DropdownMenuLabel>Filter by Tags</DropdownMenuLabel>
<DropdownMenuLabel>Filter by Expertise Areas</DropdownMenuLabel>
<DropdownMenuSeparator />
{allTags.map((tag) => (
<DropdownMenuCheckboxItem
Expand Down
File renamed without changes.
3 changes: 2 additions & 1 deletion components/admin/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
SidebarProvider,
} from "@/components/ui/sidebar"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet"
import AdminHeader from "./AdminHeader";
import CodeuniaLogo from "../codeunia-logo";

Expand Down Expand Up @@ -71,6 +71,7 @@ export function Sidebar({ avatar, name, email, role, sidebarItems, children }: S
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-80 p-0 bg-black border-r border-zinc-800">
<SheetTitle className="sr-only">Admin Navigation Menu</SheetTitle>
<div className="flex flex-col h-full">
{/* mobile header */}
<div className="p-4 border-b border-zinc-800">
Expand Down
2 changes: 1 addition & 1 deletion components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function Header() {
{ href: "/", label: "Home" },
{ href: "/about", label: "About" },
{ href: "/opportunities", label: "Opportunities" },
{ href: "/projects", label: "Projects" },
{ href: "/zenith-hall", label: "Zenith Hall" },
{ href: "/blog", label: "Blog" },
Comment on lines +61 to 62
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a redirect from /projects/zenith-hall to preserve old links.

Prevents 404s from bookmarks, SEO, and external refs.

Add to next.config.js:

 module.exports = {
   async redirects() {
     return [
+      { source: '/projects', destination: '/zenith-hall', permanent: true },
+      { source: '/projects/:path*', destination: '/zenith-hall', permanent: true },
     ];
   },
 };

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

🤖 Prompt for AI Agents
In components/header.tsx around lines 61 to 62 you added a new "Zenith Hall" nav
item but there is no redirect for legacy /projects links; to preserve old links
and SEO add a redirect in next.config.js that maps path "/projects" to
"/zenith-hall" (use a permanent redirect/301), ensure the redirects array is
exported from next.config.js and return the new rule so both dev and production
builds route /projects to /zenith-hall.

{ href: "/join", label: "Join Codeunia" },
{ href: "/contact", label: "Contact Us" },
Expand Down
3 changes: 2 additions & 1 deletion components/users/StudentSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
SidebarProvider,
} from "@/components/ui/sidebar"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet"
import CodeuniaLogo from "../codeunia-logo";


Expand Down Expand Up @@ -86,6 +86,7 @@ export function StudentSidebar({ avatar, name, email, sidebarItems, children }:
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-80 p-0 bg-black border-r border-zinc-800">
<SheetTitle className="sr-only">Student Navigation Menu</SheetTitle>
<div className="flex flex-col h-full">
{/* mobile header */}
<div className="p-4 border-b border-zinc-800">
Expand Down
13 changes: 7 additions & 6 deletions lib/security/input-validation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

// Create a DOMPurify instance for server-side use
const window = new JSDOM('').window;
const purify = DOMPurify(window as any);
import DOMPurify from 'isomorphic-dompurify';
// Works in both server and client without jsdom
const purify = DOMPurify;

export interface ValidationResult {
isValid: boolean;
Expand Down Expand Up @@ -467,6 +464,10 @@ export function withInputValidation<T extends Record<string, unknown>>(

for (const [key, options] of Object.entries(schema)) {
const value = body[key];
if (typeof value !== 'string') {
errors.push(`${key}: must be a string`);
continue;
}
Comment on lines +467 to +470
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Required-field regression: missing values now report “must be a string”.

The new typeof check fires for undefined/null, masking “required” errors and changing API behavior. Let validateText handle required-ness for absent values; reserve the type error for non-string present values.

Apply:

-        const value = body[key];
-        if (typeof value !== 'string') {
-          errors.push(`${key}: must be a string`);
-          continue;
-        }
-        const result = InputValidator.validateText(value, options);
+        const raw = body[key];
+        if (raw === undefined || raw === null) {
+          // Let validateText emit required/empty errors as appropriate
+          const result = InputValidator.validateText('', options);
+          if (!result.isValid) errors.push(`${key}: ${result.error}`);
+          else validatedData[key as keyof T] = result.sanitizedValue as T[keyof T];
+          continue;
+        }
+        if (typeof raw !== 'string') {
+          errors.push(`${key}: must be a string`);
+          continue;
+        }
+        const result = InputValidator.validateText(raw, options);
📝 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
if (typeof value !== 'string') {
errors.push(`${key}: must be a string`);
continue;
}
const raw = body[key];
if (raw === undefined || raw === null) {
// Let validateText emit required/empty errors as appropriate
const result = InputValidator.validateText('', options);
if (!result.isValid) errors.push(`${key}: ${result.error}`);
else validatedData[key as keyof T] = result.sanitizedValue as T[keyof T];
continue;
}
if (typeof raw !== 'string') {
errors.push(`${key}: must be a string`);
continue;
}
const result = InputValidator.validateText(raw, options);
🤖 Prompt for AI Agents
In lib/security/input-validation.ts around lines 467 to 470, the added typeof
check treats undefined/null as non-strings and overwrites required-field errors;
change the guard so it only flags a type error for values that are present but
not strings (e.g., if value !== undefined && value !== null && typeof value !==
'string'), allowing validateText to handle required-ness for absent values and
only continue when you intentionally handled the non-string present case.

const result = InputValidator.validateText(value, options);

if (!result.isValid) {
Expand Down
14 changes: 14 additions & 0 deletions lib/seo/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ export function generateMetadata(config: SEOConfig): Metadata {
creator: author,
publisher: defaultConfig.siteName,

// Favicon configuration
icons: {
icon: [
{ url: '/favicon.ico', type: 'image/x-icon' },
{ url: '/codeunia-favicon-light.svg', media: '(prefers-color-scheme: light)' },
{ url: '/codeunia-favicon-dark.svg', media: '(prefers-color-scheme: dark)' },
{ url: '/codeunia-favicon-light.svg' }
],
apple: [
{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' }
],
shortcut: '/favicon.ico'
},

Comment on lines +58 to +71
Copy link
Copy Markdown
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

Avoid duplicate favicon declarations and fix Apple touch icon format.

You’re defining icons here and again in app/layout.tsx; keep one source of truth (prefer Metadata). Also, iOS Safari requires a PNG for apple-touch-icon; SVG won’t render.

Apply:

-    // Favicon configuration
-    icons: {
-      icon: [
-        { url: '/favicon.ico', type: 'image/x-icon' },
-        { url: '/codeunia-favicon-light.svg', media: '(prefers-color-scheme: light)' },
-        { url: '/codeunia-favicon-dark.svg', media: '(prefers-color-scheme: dark)' },
-        { url: '/codeunia-favicon-light.svg' }
-      ],
-      apple: '/codeunia-favicon-light.svg',
-      shortcut: '/favicon.ico'
-    },
+    // Favicon configuration
+    icons: {
+      icon: [
+        { url: '/favicon.ico', type: 'image/x-icon' },
+        { url: '/codeunia-favicon-light.svg', media: '(prefers-color-scheme: light)' },
+        { url: '/codeunia-favicon-dark.svg', media: '(prefers-color-scheme: dark)' },
+        { url: '/codeunia-favicon-light.svg' }
+      ],
+      apple: [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
+      shortcut: ['/favicon.ico']
+    },

Verification script (checks files exist and flags duplicates in layout):


🏁 Script executed:

#!/bin/bash
# Icons present?
fd -a 'favicon|apple-touch' --base-directory . public || true
# Check if layout also injects <link rel="icon"> (should be removed if using Metadata)
rg -n "rel=[\"']icon|apple-touch-icon|shortcut icon" app/layout.tsx -n || true

Length of output: 686


Consolidate favicon config and fix Apple touch icon

  • Remove duplicate <link rel="icon">/<link rel="apple-touch-icon"> tags in app/layout.tsx (lines 73–78)—rely solely on the icons field in lib/seo/metadata.ts.
  • Update icons.apple to an array entry with a PNG (e.g. /apple-touch-icon.png) and sizes: '180x180', since iOS Safari doesn’t support SVG for apple-touch-icon.
🤖 Prompt for AI Agents
In lib/seo/metadata.ts around lines 58–69, the favicon configuration currently
duplicates entries and sets icons.apple to an SVG; update it to consolidate
favicon entries (remove the duplicate '/codeunia-favicon-light.svg' entry) and
replace the icons.apple string with an array entry for an apple-touch-icon using
a PNG (e.g. { url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png'
}). Also remove the duplicate <link rel="icon">/<link rel="apple-touch-icon">
tags from app/layout.tsx (lines 73–78) so the app relies solely on the icons
field in lib/seo/metadata.ts.

// Open Graph
openGraph: {
type,
Expand Down
7 changes: 7 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ const nextConfig: NextConfig = {
compress: true,
poweredByHeader: false,

async redirects() {
return [
{ source: '/projects', destination: '/zenith-hall', permanent: true },
{ source: '/projects/:path*', destination: '/zenith-hall', permanent: true },
];
},

Comment on lines +63 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Deep-link redirect drops path segments.

/projects/:path* → /zenith-hall loses the subpath. Preserve it to avoid 404s and SEO loss.

   async redirects() {
     return [
       { source: '/projects', destination: '/zenith-hall', permanent: true },
-      { source: '/projects/:path*', destination: '/zenith-hall', permanent: true },
+      { source: '/projects/:path*', destination: '/zenith-hall/:path*', permanent: true },
     ];
   },
📝 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
async redirects() {
return [
{ source: '/projects', destination: '/zenith-hall', permanent: true },
{ source: '/projects/:path*', destination: '/zenith-hall', permanent: true },
];
},
async redirects() {
return [
{ source: '/projects', destination: '/zenith-hall', permanent: true },
{ source: '/projects/:path*', destination: '/zenith-hall/:path*', permanent: true },
];
},
🤖 Prompt for AI Agents
In next.config.ts around lines 63 to 69, the redirect for deep links drops path
segments because the destination is a fixed path; update the second redirect so
it preserves the captured segments by using a matching destination with the same
wildcard (e.g., change destination to /zenith-hall/:path*), leaving the first
redirect (/projects → /zenith-hall) as-is and keeping permanent: true.

async headers() {
const isDev = process.env.NODE_ENV === 'development'
const isProd = process.env.NODE_ENV === 'production'
Expand Down
Loading
Loading