Skip to content
Merged
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
11 changes: 11 additions & 0 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { LoginForm } from "@/components/login-form"

export default function LoginPage() {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="w-full max-w-sm md:max-w-3xl">
<LoginForm />
</div>
</div>
)
}
11 changes: 11 additions & 0 deletions app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SignupForm } from "@/components/signup-form"

export default function SignupPage() {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<div className="w-full max-w-sm md:max-w-3xl">
<SignupForm />
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { auth } from "@/lib/auth";
import { auth } from "@/lib/auth/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { POST, GET } = toNextJsHandler(auth);
13 changes: 0 additions & 13 deletions app/api/spotify/auth/route.ts

This file was deleted.

46 changes: 0 additions & 46 deletions app/api/spotify/callback/route.ts

This file was deleted.

10 changes: 10 additions & 0 deletions app/api/spotify/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { NextResponse } from "next/server";

/**
* Spotify "Now Playing" API
*
* Setup (one-time):
* 1. Go to https://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI&scope=user-read-currently-playing%20user-read-playback-state
* 2. Authorize and copy the `code` from the redirect URL
* 3. Exchange it for tokens via POST to https://accounts.spotify.com/api/token
* 4. Save the refresh_token to SPOTIFY_REFRESH_TOKEN in .env.local
*/

// In-memory store for access token and expiration
let accessToken: string | null = null;
let tokenExpiration: number | null = null;
Expand Down
28 changes: 18 additions & 10 deletions components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import Image from "next/image";
import SpotifyStatus from './SpotifyStatus';
import SocialMediaLinks from './SocialMediaLinks';
import Copyright from './Copyright';
import SpotifyStatus from "./SpotifyStatus";
import SocialMediaLinks from "./SocialMediaLinks";
import Copyright from "./Copyright";
import { ASSETS } from "@/lib/constants";

export default function Footer() {
const baseUrl = "/svgs/";

return (
<footer className="relative max-w-5xl mx-auto w-full mb-3 flex flex-col rounded-2xl px-8 saturate-100 backdrop-blur-[10px] overflow-hidden bg-linear-to-t from-white/30 via-white/10 to-white/0 dark:from-black/30 dark:via-black/10 dark:to-black/0 shadow-xs">
{/* Main content container */}
<div className="relative z-10 flex flex-col md:flex-row items-center md:justify-between w-full md:gap-0">

{/* Left side: Spotify Logo and Status */}
<div className="flex items-center gap-2">
<a href="https://open.spotify.com/user/223qjmi62hl4nilhilo6lsdea" target="_blank" rel="noopener noreferrer" className="cursor-pointer">
<Image src={`${baseUrl}Spotify.svg`} alt="Spotify Logo" width={20} height={20} className="h-6 w-6" />
<a
href={process.env.Spotify_URL!}

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

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

The environment variable process.env.Spotify_URL is used here but there's no evidence this variable exists or is set. Based on the constants file and the removed callback route, this should likely use the EXTERNAL_URLS.spotifyProfile constant instead to maintain consistency with the centralization effort in this PR.

Copilot uses AI. Check for mistakes.
target="_blank"
rel="noopener noreferrer"
className="cursor-pointer"
>
<Image
src={ASSETS.spotifyIcon}
alt="Spotify Logo"
width={20}
height={20}
className="h-6 w-6"
/>
</a>
<SpotifyStatus />
</div>

{/* Right side: Social Media Links */}
<SocialMediaLinks />

</div>
<div className="relative flex justify-center items-center">
<Copyright />
</div>
</footer>
);
}
}
29 changes: 17 additions & 12 deletions components/footer/SocialMediaLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import Image from 'next/image';
import { socialMedia } from '@/lib/socialMedia';


export interface SocialMediaItem {
name: string;
url: string;
icon: string;
}
import Image from "next/image";
import { socialMedia } from "@/lib/socialMedia";

export default function SocialMediaLinks() {
return (
<div className="flex flex-row justify-center items-center gap-4 p-4">
{socialMedia.map((media) => (
<a href={media.url} target="_blank" rel="noopener noreferrer" key={media.name} className="cursor-pointer">
<Image src={media.icon} alt={`${media.name} Icon`} width={20} height={20} className="filter dark:invert" />
<a
href={media.url}
target="_blank"
rel="noopener noreferrer"
key={media.name}
className="cursor-pointer"
>
<Image
src={media.icon}
alt={`${media.name} Icon`}
width={20}
height={20}
className="filter dark:invert"
/>
</a>
))}
</div>
);
}
}
45 changes: 21 additions & 24 deletions components/footer/SpotifyStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
'use client';
import { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
"use client";
import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import type { SpotifyStatus } from "@/lib/types";

interface SpotifyStatus {
isListening: boolean;
trackName?: string;
artistName?: string;
message?: string;
itemType?: string;
}

export default function SpotifyStatus() {
export default function SpotifyStatusComponent() {

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

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

The function name SpotifyStatusComponent is inconsistent with the file name SpotifyStatus.tsx. The function should be named SpotifyStatus to match the file name and the import usage in Footer.tsx, following React component naming conventions.

Suggested change
export default function SpotifyStatusComponent() {
export default function SpotifyStatus() {

Copilot uses AI. Check for mistakes.
const [status, setStatus] = useState<SpotifyStatus>({ isListening: false });

useEffect(() => {
async function fetchSpotifyStatus() {
try {
const response = await fetch('api/spotify');
const response = await fetch("api/spotify");
const data: SpotifyStatus = await response.json();
setStatus(data);
} catch {
setStatus({ isListening: false, message: 'Error fetching Spotify data' });
setStatus({ isListening: false, message: "Error fetching Spotify data" });
}
}
fetchSpotifyStatus();
Expand All @@ -30,23 +23,27 @@ export default function SpotifyStatus() {

const formatNowPlaying = () => {
if (!status.isListening) {
return status.message || 'Not listening to Spotify';
return status.message || "Not listening to Spotify";
}

if (status.itemType === 'episode') {
if (status.itemType === "episode") {
return `Now Playing: ${status.trackName} from ${status.artistName}`;
} else {
return `Now Playing: ${status.trackName} by ${status.artistName}`;
}
};

return (
<>
<AnimatePresence mode="wait">
<motion.p key={`${status.isListening}-${status.trackName}`} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.3 }} className="">
{formatNowPlaying()}
</motion.p>
</AnimatePresence>
</>
<AnimatePresence mode="wait">
<motion.p
key={`${status.isListening}-${status.trackName}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
{formatNowPlaying()}
</motion.p>
</AnimatePresence>
);
}
}
37 changes: 37 additions & 0 deletions components/header/MobileMenuToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";
import { useState } from "react";
import NavbarLinks from "./NavbarLinks";

export default function MobileMenuToggle() {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<button
className="md:hidden text-foreground"
onClick={() => setIsOpen((prev) => !prev)}
aria-label={isOpen ? "Close menu" : "Open menu"}
aria-expanded={isOpen}
>
<svg className="w-8 h-8" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
d={isOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16M4 18h16"}
/>
</svg>
</button>

{isOpen && (
<div className="absolute top-full left-0 right-0 z-50 md:hidden px-4 mt-2 animate-in fade-in slide-in-from-top-2 duration-300">
<div className="py-4 flex flex-col bg-background border rounded-2xl shadow-xl w-full">
<NavbarLinks
className="flex flex-col items-center space-y-4 w-full"
onLinkClick={() => setIsOpen(false)}
/>
</div>
</div>
)}
</>
);
}
Comment on lines +5 to +37

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

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

The mobile menu doesn't close when clicking outside of it or pressing the Escape key. Consider adding event listeners to improve the user experience by closing the menu when users click outside the menu area or press Escape.

Copilot uses AI. Check for mistakes.
Loading
Loading