Skip to content

Commit cef3102

Browse files
committed
Add favorites page
1 parent daf3b86 commit cef3102

File tree

7 files changed

+390
-2
lines changed

7 files changed

+390
-2
lines changed

app/favorites/layout.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Metadata } from "next";
2+
3+
export const metadata: Metadata = {
4+
title: "Favorite Presets | NUX Mighty Plug Pro & Mighty Space | nxrig",
5+
description:
6+
"Your favorite presets for NUX Mighty Plug Pro and Mighty Space. Save and manage your favorite guitar tones.",
7+
alternates: {
8+
canonical: "https://nxrig.com/favorites",
9+
},
10+
openGraph: {
11+
title: "Favorite Presets | NUX Mighty Plug Pro & Mighty Space | nxrig",
12+
description:
13+
"Your favorite presets for NUX Mighty Plug Pro and Mighty Space. Save and manage your favorite guitar tones.",
14+
url: "https://nxrig.com/favorites",
15+
type: "website",
16+
},
17+
twitter: {
18+
card: "summary_large_image",
19+
},
20+
};
21+
22+
export default function FavoritesLayout({
23+
children,
24+
}: {
25+
children: React.ReactNode;
26+
}) {
27+
return children;
28+
}

app/favorites/page.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use client";
2+
3+
import { useFavorites } from "hooks/useFavorites";
4+
import { presets } from "lib/public/presets";
5+
import Header from "components/Header";
6+
import Footer from "components/Footer";
7+
import { PresetCard } from "components/PresetCard";
8+
import { ReactElement } from "react";
9+
import Link from "next/link";
10+
11+
export default function FavoritesPage(): ReactElement {
12+
const { favorites, isLoaded } = useFavorites();
13+
14+
// Фильтруем пресеты по избранным ID
15+
const favoritePresets = presets.filter((preset) =>
16+
favorites.includes(preset.id),
17+
);
18+
19+
// Отслеживаем просмотр страницы избранного
20+
useEffect(() => {
21+
if (isLoaded) {
22+
trackViewFavorites(favoritePresets.length);
23+
}
24+
}, [isLoaded, favoritePresets.length]);
25+
26+
return (
27+
<div className="min-h-screen flex flex-col bg-gray-900 text-white">
28+
<Header />
29+
<main className="flex-grow">
30+
<div className="container mx-auto px-4 py-8">
31+
<div className="flex items-center gap-4 mb-6">
32+
<svg
33+
width="32"
34+
height="32"
35+
viewBox="0 0 24 24"
36+
fill="currentColor"
37+
className="text-pink-400"
38+
>
39+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
40+
</svg>
41+
<h1 className="text-4xl font-bold text-white">Favorite Presets</h1>
42+
</div>
43+
44+
<p className="mb-8 text-gray-300 leading-relaxed">
45+
Here are all your favorite presets for{" "}
46+
<strong>NUX Mighty Plug Pro</strong> and{" "}
47+
<strong>Mighty Space</strong>. Add your favorite tones to quickly
48+
find them later.
49+
</p>
50+
51+
{!isLoaded ? (
52+
<div className="flex items-center justify-center py-12">
53+
<div className="text-gray-400">Loading favorite presets...</div>
54+
</div>
55+
) : favoritePresets.length === 0 ? (
56+
<div className="text-center py-12">
57+
<div className="mb-4">
58+
<svg
59+
width="64"
60+
height="64"
61+
viewBox="0 0 24 24"
62+
fill="none"
63+
stroke="currentColor"
64+
strokeWidth="1"
65+
strokeLinecap="round"
66+
strokeLinejoin="round"
67+
className="mx-auto text-gray-500"
68+
>
69+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
70+
</svg>
71+
</div>
72+
<h2 className="text-xl font-semibold mb-2 text-gray-300">
73+
No favorite presets yet
74+
</h2>
75+
<p className="text-gray-400 mb-6">
76+
Add presets to favorites by clicking the star on preset cards.
77+
</p>
78+
<Link
79+
href="/preset"
80+
className="inline-flex items-center gap-2 px-6 py-3 bg-pink-500 hover:bg-pink-600 text-white rounded-lg transition-colors"
81+
>
82+
Browse All Presets
83+
</Link>
84+
</div>
85+
) : (
86+
<>
87+
<div className="mb-6 text-sm text-gray-400">
88+
Found {favoritePresets.length} favorite preset
89+
{favoritePresets.length !== 1 ? "s" : ""}
90+
</div>
91+
92+
<div className="grid gap-6">
93+
{favoritePresets.map((preset) => (
94+
<PresetCard key={preset.id} preset={preset} />
95+
))}
96+
</div>
97+
</>
98+
)}
99+
</div>
100+
</main>
101+
<Footer>
102+
<div className="container mx-auto px-4">
103+
<p className="text-gray-300 text-center">
104+
Manage your favorite presets for NUX Mighty Plug Pro and Mighty
105+
Space.
106+
</p>
107+
</div>
108+
</Footer>
109+
</div>
110+
);
111+
}

components/FavoriteButton.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use client";
2+
3+
import { useFavorites } from "hooks/useFavorites";
4+
import { ReactElement } from "react";
5+
6+
interface FavoriteButtonProps {
7+
presetId: string;
8+
variant?: "default" | "compact";
9+
className?: string;
10+
}
11+
12+
export function FavoriteButton({
13+
presetId,
14+
variant = "default",
15+
className = "",
16+
}: FavoriteButtonProps): ReactElement {
17+
const { isFavorite, toggleFavorite, isLoaded } = useFavorites();
18+
19+
const isInFavorites = isFavorite(presetId);
20+
21+
// Не показываем кнопку пока не загрузились данные из localStorage
22+
if (!isLoaded) {
23+
return (
24+
<div
25+
className={`${variant === "compact" ? "w-8 h-8" : "w-10 h-10"} ${className}`}
26+
/>
27+
);
28+
}
29+
30+
const handleClick = (e: React.MouseEvent) => {
31+
e.preventDefault(); // Предотвращаем навигацию если кнопка внутри Link
32+
e.stopPropagation();
33+
toggleFavorite(presetId);
34+
};
35+
36+
if (variant === "compact") {
37+
return (
38+
<button
39+
onClick={handleClick}
40+
className={`w-8 h-8 flex items-center justify-center rounded-full transition-all duration-200 hover:scale-110 ${
41+
isInFavorites
42+
? "text-pink-400 hover:text-pink-300"
43+
: "text-gray-400 hover:text-pink-400"
44+
} ${className}`}
45+
title={isInFavorites ? "Remove from favorites" : "Add to favorites"}
46+
aria-label={
47+
isInFavorites ? "Remove from favorites" : "Add to favorites"
48+
}
49+
>
50+
<svg
51+
width="20"
52+
height="20"
53+
viewBox="0 0 24 24"
54+
fill={isInFavorites ? "currentColor" : "none"}
55+
stroke="currentColor"
56+
strokeWidth="2"
57+
strokeLinecap="round"
58+
strokeLinejoin="round"
59+
>
60+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
61+
</svg>
62+
</button>
63+
);
64+
}
65+
66+
return (
67+
<button
68+
onClick={handleClick}
69+
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg transition-all duration-200 border ${
70+
isInFavorites
71+
? "bg-pink-500/20 border-pink-500/50 text-pink-300 hover:bg-pink-500/30"
72+
: "bg-gray-700/50 border-gray-600 text-gray-300 hover:bg-gray-600/50 hover:border-pink-500/50 hover:text-pink-400"
73+
} ${className}`}
74+
>
75+
<svg
76+
width="18"
77+
height="18"
78+
viewBox="0 0 24 24"
79+
fill={isInFavorites ? "currentColor" : "none"}
80+
stroke="currentColor"
81+
strokeWidth="2"
82+
strokeLinecap="round"
83+
strokeLinejoin="round"
84+
>
85+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
86+
</svg>
87+
{isInFavorites ? "In Favorites" : "Add to Favorites"}
88+
</button>
89+
);
90+
}

components/Header.tsx

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const Header: React.FC = () => {
1111
return (
1212
<header className="bg-gray-800/50 backdrop-blur-sm border-b border-white/10">
1313
<div className="container mx-auto px-4 py-4">
14-
<div className="flex items-center justify-between w-full">
14+
{/* Desktop Layout */}
15+
<div className="hidden md:flex items-center justify-between w-full">
1516
<div className="flex items-center gap-6">
1617
<Link
1718
href="/"
@@ -27,6 +28,29 @@ const Header: React.FC = () => {
2728

2829
{!isAdminPage && (
2930
<nav className="flex items-center gap-6">
31+
<Link
32+
href="/favorites"
33+
className={`hover:text-pink-400 transition-colors flex items-center gap-2 ${
34+
pathname === "/favorites"
35+
? "text-pink-400 font-medium"
36+
: "text-gray-300"
37+
}`}
38+
title="Favorite Presets"
39+
>
40+
<svg
41+
width="18"
42+
height="18"
43+
viewBox="0 0 24 24"
44+
fill={pathname === "/favorites" ? "currentColor" : "none"}
45+
stroke="currentColor"
46+
strokeWidth="2"
47+
strokeLinecap="round"
48+
strokeLinejoin="round"
49+
>
50+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
51+
</svg>
52+
<span>Favorites</span>
53+
</Link>
3054
<Link
3155
href="/order"
3256
className={`hover:text-pink-400 transition-colors px-4 py-2 rounded-lg border border-pink-500/30 bg-pink-500/10 ${
@@ -40,6 +64,59 @@ const Header: React.FC = () => {
4064
</nav>
4165
)}
4266
</div>
67+
68+
{/* Mobile Layout */}
69+
<div className="md:hidden">
70+
<div className="flex items-center justify-between mb-2">
71+
<Link
72+
href="/"
73+
className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500"
74+
>
75+
nxrig.com
76+
</Link>
77+
78+
{!isAdminPage && (
79+
<nav className="flex items-center gap-3">
80+
<Link
81+
href="/favorites"
82+
className={`hover:text-pink-400 transition-colors flex items-center gap-1 ${
83+
pathname === "/favorites"
84+
? "text-pink-400 font-medium"
85+
: "text-gray-300"
86+
}`}
87+
title="Favorite Presets"
88+
>
89+
<svg
90+
width="20"
91+
height="20"
92+
viewBox="0 0 24 24"
93+
fill={pathname === "/favorites" ? "currentColor" : "none"}
94+
stroke="currentColor"
95+
strokeWidth="2"
96+
strokeLinecap="round"
97+
strokeLinejoin="round"
98+
>
99+
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
100+
</svg>
101+
</Link>
102+
<Link
103+
href="/order"
104+
className={`hover:text-pink-400 transition-colors px-3 py-1.5 text-sm rounded-lg border border-pink-500/30 bg-pink-500/10 ${
105+
pathname === "/order"
106+
? "text-pink-400 font-medium border-pink-400"
107+
: "text-pink-300"
108+
}`}
109+
>
110+
Request
111+
</Link>
112+
</nav>
113+
)}
114+
</div>
115+
116+
<p className="text-gray-400 text-sm leading-tight">
117+
Professional presets for NUX Mighty Plug Pro & Mighty Space
118+
</p>
119+
</div>
43120
</div>
44121

45122
{isAdminPage && (

components/PresetCard.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Preset } from "lib/public/interface";
77
import { encodeChain } from "lib/core/encoder";
88
import { createPresetLink } from "lib/utils/urls";
99
import { CompatibleDevices } from "./DeviceBadge";
10+
import { FavoriteButton } from "./FavoriteButton";
1011

1112
interface PresetCardProps {
1213
preset: Preset;
@@ -59,6 +60,11 @@ export function PresetCard({ preset }: PresetCardProps): React.ReactElement {
5960
<CompatibleDevices />
6061
</div>
6162
</div>
63+
64+
{/* Favorite Button */}
65+
<div className="shrink-0">
66+
<FavoriteButton presetId={preset.id} variant="compact" />
67+
</div>
6268
</div>
6369
</div>
6470
</Link>

components/PresetDetails.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ChainEditor from "./chain/ChainEditor";
88
import { CompatibleDevices } from "./DeviceBadge";
99
import Link from "next/link";
1010
import { createArtistLink } from "lib/utils/urls";
11+
import { FavoriteButton } from "./FavoriteButton";
1112

1213
interface PresetDetailsProps {
1314
preset: Preset;
@@ -97,7 +98,7 @@ export const PresetDetails: FC<PresetDetailsProps> = ({ preset }) => {
9798
</ol>
9899
</div>
99100

100-
<div className="mt-8">
101+
<div className="mt-8 flex items-center gap-4">
101102
<button
102103
onClick={() => {
103104
const canvas = document.querySelector("canvas");
@@ -118,6 +119,8 @@ export const PresetDetails: FC<PresetDetailsProps> = ({ preset }) => {
118119
>
119120
Download «{preset.origin.song} {preset.origin.part}» patch file
120121
</button>
122+
123+
<FavoriteButton presetId={preset.id} />
121124
</div>
122125

123126
<div className="mt-8">

0 commit comments

Comments
 (0)