Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions src/pages/podcasts/details.css
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,15 @@ html[data-theme='light'] {
justify-content: center;
}

.nav-action-button:active {
box-shadow: 0 1px 8px rgba(40,50,70,0.06);
}

.nav-action-button.favorite.favorited {
color: #ff4d4d; /* red heart */
background: #fff0f0; /* soft pink background when liked */
}

.nav-action-button:hover {
background: var(--details-bg-card-hover);
color: var(--details-text-primary);
Expand Down
50 changes: 41 additions & 9 deletions src/pages/podcasts/details.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Layout from '@theme/Layout';
import type { ReactElement } from 'react';
import { useLocation, useHistory } from '@docusaurus/router';
Expand Down Expand Up @@ -85,7 +85,26 @@ export default function PodcastDetails(): ReactElement {
const history = useHistory();
const state = location.state as LocationState;
const podcast = state?.podcast;


const [favorites, setFavorites] = useState<string[]>(() => {
if (typeof window !== "undefined") {
const saved = localStorage.getItem("podcast-favorites");
return saved ? JSON.parse(saved) : [];
}
return [];
});
const isFavorited = podcast ? favorites.includes(podcast.id) : false;
const toggleFavorite = () => {
if (!podcast) return;
setFavorites(prev => {
const updated = prev.includes(podcast.id)
? prev.filter(id => id !== podcast.id)
: [...prev, podcast.id];
localStorage.setItem("podcast-favorites", JSON.stringify(updated));
return updated;
});
};

// Enhanced descriptions with categories
const descriptions = {
episode: [
Expand Down Expand Up @@ -174,13 +193,26 @@ export default function PodcastDetails(): ReactElement {
<span className="nav-text">Back to Podcasts</span>
</button>
<div className="nav-actions">
<button className="nav-action-button" onClick={handleShare} title="Share">
<span className="action-icon">🔗</span>
</button>
<button className="nav-action-button" title="Add to favorites">
<span className="action-icon">❤️</span>
</button>
</div>
<button
className="nav-action-button"
onClick={handleShare}
title="Share"
>
Comment on lines 196 to 200
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The handleShare function expects a podcast parameter but is being called without any arguments. This will cause a runtime error when the share button is clicked.

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

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

try to resolve this one

<span className="action-icon">🔗</span>
</button>
<button
className={`nav-action-button favorite ${isFavorited ? "favorited" : ""}`}
title={isFavorited ? "Remove from favorites" : "Add to favorites"}
onClick={e => {
e.preventDefault();
e.stopPropagation();
toggleFavorite();
}}
>
<span className="action-icon">{isFavorited ? "❤️" : "🤍"}</span>
</button>
</div>

</div>

{/* Main Content */}
Expand Down
56 changes: 56 additions & 0 deletions src/pages/podcasts/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,12 @@ html[data-theme='light'] {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
position: relative;
z-index: 1;
overflow: hidden;
animation: fadeInUp 0.6s ease-out both;
}


.enhanced-podcast-card::before {
content: '';
position: absolute;
Expand Down Expand Up @@ -404,12 +406,42 @@ html[data-theme='light'] {
gap: 8px;
opacity: 0;
transition: opacity 0.3s ease;
position: relative;
z-index: 10;
pointer-events: auto;
}


.enhanced-podcast-card:hover .card-actions {
opacity: 1;
}

.action-btn.share {
background-color: rgba(123, 124, 128, 0.15); /* same subtle bg as unfavorited */
border: 1px solid rgba(123, 124, 128, 0.3); /* same border */
transition: all 0.3s ease;
color: inherit; /* keep the existing icon color */
border-radius: 8px;
width: 36px;
height: 36px;
cursor: pointer;
font-size: 14px;
position: relative;
z-index: 15;
user-select: none;
}

.action-btn.share:hover {
background-color: rgba(123, 124, 128, 0.25); /* slightly darker on hover */
border-color: rgba(123, 124, 128, 0.5);
transform: scale(1.1);
}

.action-btn.share:active {
transform: scale(0.95);
}


.action-btn {
width: 36px;
height: 36px;
Expand All @@ -419,12 +451,36 @@ html[data-theme='light'] {
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
position: relative;
z-index: 15;
pointer-events: auto !important;
user-select: none;
}


.action-btn:hover {
background: var(--podcast-bg-card-hover);
transform: scale(1.1);
}
/* Enhanced favorite button styling */
.action-btn.favorite {
transition: all 0.2s ease;
border: 1px solid transparent;
}

.action-btn.favorite:active {
transform: scale(0.95);
}

.action-btn.favorite.unfavorited {
background-color: rgba(123, 124, 128, 0.15); /* light purple-blue tint */
color: white; /* keep the heart white */
border-color: rgba(123, 124, 128, 0.3);
}

.action-btn.favorite.unfavorited:hover {
background-color: rgba(123, 124, 128, 0.15);
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The hover state for unfavorited buttons has the same background-color as the default state, making the hover effect invisible. Consider using a different color or opacity for the hover state.

Suggested change
background-color: rgba(123, 124, 128, 0.15);
background-color: rgba(123, 124, 128, 0.25); /* increased opacity for visible hover effect */

Copilot uses AI. Check for mistakes.

}

/* Podcast Title */
.podcast-title {
Expand Down
121 changes: 95 additions & 26 deletions src/pages/podcasts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,19 @@ const SpotifyTitle: React.FC<SpotifyTitleProps> = ({ spotifyUrl, type }) => {
export default function Podcasts(): ReactElement {
const history = useHistory();
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState('');
const [selectedFilter, setSelectedFilter] = useState<'all' | 'episode' | 'show' | 'playlist'>('all');
const [searchTerm, setSearchTerm] = useState("");
const [selectedFilter, setSelectedFilter] = useState<
"all" | "episode" | "show" | "playlist"
>("all");
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent quote style. The codebase should use consistent quotes throughout - either single quotes (') or double quotes ("). Consider using single quotes to match the existing pattern in the file.

Suggested change
>("all");
>('all');

Copilot uses AI. Check for mistakes.

// ADD THIS NEW STATE FOR FAVORITES
const [favorites, setFavorites] = useState<string[]>(() => {
// Load favorites from localStorage on component mount
if (typeof window !== "undefined") {
const saved = localStorage.getItem("podcast-favorites");
return saved ? JSON.parse(saved) : [];
}
return [];
});
const podcastsPerPage = 9;

// Filter podcasts based on search and filter
Expand Down Expand Up @@ -136,17 +147,47 @@ export default function Podcasts(): ReactElement {
}
};

const handlePodcastClick = (podcast: PodcastData, event: React.MouseEvent | React.KeyboardEvent) => {
// ADD THIS NEW FUNCTION FOR FAVORITES
const handleFavorite = (podcast: PodcastData, event: React.MouseEvent) => {
// Prevent card click when clicking favorite button
event.stopPropagation();

setFavorites((prev) => {
const isFavorited = prev.includes(podcast.id);
const newFavorites = isFavorited
? prev.filter((id) => id !== podcast.id) // Remove from favorites
: [...prev, podcast.id]; // Add to favorites

// Save to localStorage for persistence
if (typeof window !== "undefined") {
localStorage.setItem("podcast-favorites", JSON.stringify(newFavorites));
}

return newFavorites;
});
};

const handlePodcastClick = (
podcast: PodcastData,
event: React.MouseEvent | React.KeyboardEvent
) => {
const target = event.target as HTMLElement;
if (target.tagName === 'IFRAME' || target.closest('.podcast-embed')) {

// Prevent navigation if clicking on buttons or action area
if (
target.tagName === "IFRAME" ||
target.closest(".podcast-embed") ||
target.closest(".action-btn") || // Don't navigate if clicking buttons
target.closest(".card-actions") || // Don't navigate if clicking action area
target.classList.contains("action-btn") ||
target.classList.contains("favorite") ||
target.classList.contains("share")
) {
return;
}
history.push('/podcasts/details', { podcast });
};

React.useEffect(() => {
setCurrentPage(1);
}, [searchTerm, selectedFilter]);
history.push("/podcasts/details", { podcast });
};

return (
<Layout>
Expand All @@ -164,7 +205,7 @@ export default function Podcasts(): ReactElement {
<p className="podcast-hero-description">
Stream the best podcasts from your favorite stations. Dive into episodes that inspire, educate, and entertain from leading voices in tech, business, and beyond.
</p>

{/* Stats */}
<div className="podcast-stats">
<div className="stat-item">
Expand Down Expand Up @@ -247,20 +288,48 @@ export default function Podcasts(): ReactElement {
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="podcast-card-header">
<SpotifyTitle spotifyUrl={podcast.spotifyUrl} type={podcast.type} />
<div className="card-actions">
<button className="action-btn favorite" title="Add to favorites">
❤️
<SpotifyTitle
spotifyUrl={podcast.spotifyUrl}
type={podcast.type}
/>
<div
className="card-actions"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
onMouseDown={(e) => {
e.stopPropagation();
}}
>
<button
className={`action-btn favorite unfavorited ${
favorites.includes(podcast.id) ? "favorited" : ""
}`}
Comment on lines +304 to +306
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The className logic is confusing - applying both 'unfavorited' and potentially 'favorited' classes. Consider using conditional logic: className={action-btn favorite ${favorites.includes(podcast.id) ? 'favorited' : 'unfavorited'}}

Suggested change
className={`action-btn favorite unfavorited ${
favorites.includes(podcast.id) ? "favorited" : ""
}`}
className={`action-btn favorite ${favorites.includes(podcast.id) ? 'favorited' : 'unfavorited'}`}

Copilot uses AI. Check for mistakes.

title={
favorites.includes(podcast.id)
? "Remove from favorites"
: "Add to favorites"
}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
Comment on lines +313 to +315
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

Multiple event propagation prevention methods are redundant. e.nativeEvent.stopImmediatePropagation() is unnecessary when e.stopPropagation() is already used, and handleFavorite already calls event.stopPropagation(). Consider simplifying to just e.stopPropagation() or move all event handling to the handleFavorite function.

Suggested change
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();

Copilot uses AI. Check for mistakes.

handleFavorite(podcast, e);
}}
>
{favorites.includes(podcast.id) ? '❤️' : '🤍'}

</button>
<button className="action-btn share" title="Share podcast" onClick={(e) => {
e.stopPropagation();
handleShare(podcast);
e.stopPropagation();
handleShare(podcast);
}}>
🔗
</button>
</div>
</div>

<div className="podcast-embed" onClick={(e) => e.stopPropagation()}>
<iframe
src={`https://open.spotify.com/embed/${podcast.type}/${getSpotifyEmbedId(podcast.spotifyUrl)}`}
Expand All @@ -273,7 +342,7 @@ export default function Podcasts(): ReactElement {
title={`Spotify embed ${podcast.id}`}
/>
</div>

<div className="podcast-card-footer">
<button className="listen-button">
<span className="listen-icon">▶️</span>
Expand All @@ -294,19 +363,19 @@ export default function Podcasts(): ReactElement {
>
← Previous
</button>

<div className="pagination-numbers">
{Array.from({ length: totalPages }, (_, i) => i + 1).map((number) => (
<button
key={number}
<button
key={number}
className={`pagination-number ${currentPage === number ? 'active' : ''}`}
onClick={() => handlePageChange(number)}
>
{number}
</button>
onClick={() => handlePageChange(number)}
>
{number}
</button>
))}
</div>

<button
className="pagination-nav"
onClick={() => handlePageChange(Math.min(totalPages, currentPage + 1))}
Expand Down