Skip to content

Commit 341cdce

Browse files
committed
feat: add song(s) download functionality
Signed-off-by: rajput-hemant <[email protected]>
1 parent 12fb881 commit 341cdce

File tree

14 files changed

+282
-111
lines changed

14 files changed

+282
-111
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"dev": "next dev",
7+
"dev": "next dev --turbo",
88
"build": "next build",
99
"start": "next start",
1010
"preview": "next build && next start",
@@ -19,7 +19,7 @@
1919
"db:pull": "drizzle-kit introspect",
2020
"db:studio": "drizzle-kit studio",
2121
"db:check": "drizzle-kit check",
22-
"ui": "bunx shadcn-ui",
22+
"ui": "bunx shadcn",
2323
"clean": "rm -rf .next",
2424
"cleani": "rm -rf .next && rm -rf node_modules && bun i",
2525
"prepare": "husky"

src/app/(root)/settings/_components/preference-settings.tsx

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { setCookie } from "cookies-next";
66
import { ChevronDown } from "lucide-react";
77
import { toast } from "sonner";
88

9-
import type { ImageQuality, Lang, StreamQuality } from "@/types";
9+
import type { ImageQuality, Lang } from "@/types";
1010

1111
import { Button } from "@/components/ui/button";
1212
import {
@@ -24,14 +24,7 @@ import {
2424
useStreamQuality,
2525
} from "@/hooks/use-store";
2626
import { cn } from "@/lib/utils";
27-
28-
const SONG_QUALITIES: { quality: StreamQuality; bitrate: string }[] = [
29-
{ quality: "poor", bitrate: "12kbps" },
30-
{ quality: "low", bitrate: "48kbps" },
31-
{ quality: "medium", bitrate: "96kbps" },
32-
{ quality: "high", bitrate: "160kbps" },
33-
{ quality: "excellent", bitrate: "320kbps" },
34-
];
27+
import { QUALITIES_MAP } from "@/types";
3528

3629
const IMAGE_QUALITIES: ImageQuality[] = ["low", "medium", "high"];
3730

@@ -101,23 +94,21 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
10194
id="stream-quality"
10295
className="flex max-w-xl items-center justify-between"
10396
>
104-
<h4 className="w-40 text-muted-foreground md:text-lg">
105-
Stream Quality
106-
</h4>
97+
<h4 className="w-40 text-muted-foreground">Stream Quality</h4>
10798

10899
<Separator className="w-20" />
109100

110101
<DropdownMenu>
111102
<DropdownMenuTrigger asChild>
112103
<Button
113104
variant="outline"
114-
className="group w-44 justify-between font-semibold capitalize"
105+
className="group w-48 justify-between font-semibold capitalize"
115106
>
116107
<span>{streamQuality}</span>
117-
<span className="font-light">
108+
<span className="text-xs font-light">
118109
(
119110
{
120-
SONG_QUALITIES.find((q) => q.quality === streamQuality)
111+
QUALITIES_MAP.find((q) => q.quality === streamQuality)
121112
?.bitrate
122113
}
123114
)
@@ -127,8 +118,8 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
127118
</Button>
128119
</DropdownMenuTrigger>
129120

130-
<DropdownMenuContent className="w-44 *:cursor-pointer *:capitalize">
131-
{SONG_QUALITIES.map(({ quality, bitrate }) => (
121+
<DropdownMenuContent className="w-48 *:cursor-pointer *:capitalize">
122+
{QUALITIES_MAP.map(({ quality, bitrate }) => (
132123
<DropdownMenuItem
133124
key={quality}
134125
onClick={() => {
@@ -144,7 +135,7 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
144135
)}
145136
>
146137
<span>{quality}</span>
147-
<span className="font-medium">{bitrate}</span>
138+
<span className="text-xs font-medium">{bitrate}</span>
148139
</DropdownMenuItem>
149140
))}
150141
</DropdownMenuContent>
@@ -155,23 +146,21 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
155146
id="download-quality"
156147
className="flex max-w-xl items-center justify-between"
157148
>
158-
<h4 className="w-40 text-muted-foreground md:text-lg">
159-
Download Quality
160-
</h4>
149+
<h4 className="w-40 text-muted-foreground">Download Quality</h4>
161150

162151
<Separator className="w-20" />
163152

164153
<DropdownMenu>
165154
<DropdownMenuTrigger asChild>
166155
<Button
167156
variant="outline"
168-
className="group w-44 justify-between font-semibold capitalize"
157+
className="group w-48 justify-between font-semibold capitalize"
169158
>
170159
<span>{streamQuality}</span>
171-
<span className="font-light">
160+
<span className="text-xs font-light">
172161
(
173162
{
174-
SONG_QUALITIES.find((q) => q.quality === downloadQuality)
163+
QUALITIES_MAP.find((q) => q.quality === downloadQuality)
175164
?.bitrate
176165
}
177166
)
@@ -180,8 +169,8 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
180169
</Button>
181170
</DropdownMenuTrigger>
182171

183-
<DropdownMenuContent className="w-44 *:cursor-pointer *:capitalize">
184-
{SONG_QUALITIES.map(({ bitrate, quality }) => (
172+
<DropdownMenuContent className="w-48 *:cursor-pointer *:capitalize">
173+
{QUALITIES_MAP.map(({ bitrate, quality }) => (
185174
<DropdownMenuItem
186175
key={quality}
187176
onClick={() => {
@@ -197,7 +186,7 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
197186
)}
198187
>
199188
<span>{quality}</span>
200-
<span className="font-medium">{bitrate}</span>
189+
<span className="text-xs font-medium">{bitrate}</span>
201190
</DropdownMenuItem>
202191
))}
203192
</DropdownMenuContent>
@@ -208,24 +197,22 @@ export function PreferenceSettings(props: PreferenceSettingsProps) {
208197
id="image-quality"
209198
className="flex max-w-xl items-center justify-between"
210199
>
211-
<h4 className="w-40 text-muted-foreground md:text-lg">
212-
Image Quality
213-
</h4>
200+
<h4 className="w-40 text-muted-foreground">Image Quality</h4>
214201

215202
<Separator className="w-20" />
216203

217204
<DropdownMenu>
218205
<DropdownMenuTrigger asChild>
219206
<Button
220207
variant="outline"
221-
className="group w-44 justify-between font-semibold capitalize"
208+
className="group w-48 justify-between font-semibold capitalize"
222209
>
223210
{imageQuality}
224211
<ChevronDown className="ml-2 size-4 transition-transform group-data-[state=open]:rotate-180" />
225212
</Button>
226213
</DropdownMenuTrigger>
227214

228-
<DropdownMenuContent className="w-44 *:cursor-pointer *:capitalize">
215+
<DropdownMenuContent className="w-48 *:cursor-pointer *:capitalize">
229216
{IMAGE_QUALITIES.map((quality) => (
230217
<DropdownMenuItem
231218
key={quality}

src/app/layout.tsx

Lines changed: 48 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,16 @@ import React from "react";
44
import { cookies } from "next/headers";
55
import Script from "next/script";
66

7+
import type { Metadata, Viewport } from "next";
78
import type { ThemeConfig } from "@/types";
89

910
import Providers from "@/components/provider";
1011
import { TailwindIndicator } from "@/components/tailwind-indicator";
1112
import { siteConfig } from "@/config/site";
1213
import { env } from "@/lib/env";
13-
import { fontHeading, fontMono, fontSans } from "@/lib/fonts";
14+
import * as fonts from "@/lib/fonts";
1415
import { absoluteUrl, cn } from "@/lib/utils";
1516

16-
export const viewport = {
17-
viewportFit: "cover",
18-
width: "device-width",
19-
initialScale: 1,
20-
maximumScale: 5,
21-
userScalable: true,
22-
themeColor: [
23-
{ media: "(prefers-color-scheme: light)", color: "white" },
24-
{ media: "(prefers-color-scheme: dark)", color: "black" },
25-
],
26-
};
27-
28-
export const metadata = {
29-
title: {
30-
default: siteConfig.name,
31-
template: `%s | ${siteConfig.name}`,
32-
},
33-
description: siteConfig.description,
34-
authors: {
35-
name: siteConfig.author.name,
36-
url: siteConfig.author.url,
37-
},
38-
creator: siteConfig.author.name,
39-
openGraph: {
40-
type: "website",
41-
locale: "en_US",
42-
url: siteConfig.url,
43-
title: siteConfig.name,
44-
description: siteConfig.description,
45-
siteName: siteConfig.name,
46-
},
47-
twitter: {
48-
card: "summary_large_image",
49-
title: siteConfig.name,
50-
description: siteConfig.description,
51-
creator: siteConfig.author.x,
52-
},
53-
icons: {
54-
icon: [
55-
{ url: "/favicon.ico", sizes: "32x32" },
56-
{ url: "/favicon-16x16.png", type: "image/png", sizes: "16x16" },
57-
{ url: "/favicon-32x32.png", type: "image/png", sizes: "32x32" },
58-
{ url: "/icon-192.png", type: "image/png", sizes: "192x192" },
59-
{ url: "/icon-512.png", type: "image/png", sizes: "512x512" },
60-
],
61-
apple: [{ url: "/apple-icon.png", type: "image/png" }],
62-
},
63-
metadataBase: new URL(absoluteUrl("/")),
64-
};
65-
6617
type RootLayoutProps = {
6718
modal: React.ReactNode;
6819
children: React.ReactNode;
@@ -78,14 +29,10 @@ export default function RootLayout({ modal, children }: RootLayoutProps) {
7829
return (
7930
<React.StrictMode>
8031
<html lang="en" suppressHydrationWarning>
81-
<head />
82-
8332
<body
8433
className={cn(
34+
Object.values(fonts).map((font) => font.variable),
8535
"min-h-screen font-sans antialiased",
86-
fontSans.variable,
87-
fontMono.variable,
88-
fontHeading.variable,
8936
`theme-${theme}`
9037
)}
9138
style={
@@ -112,3 +59,48 @@ export default function RootLayout({ modal, children }: RootLayoutProps) {
11259
</React.StrictMode>
11360
);
11461
}
62+
63+
export const viewport: Viewport = {
64+
viewportFit: "cover",
65+
width: "device-width",
66+
initialScale: 1,
67+
maximumScale: 5,
68+
userScalable: true,
69+
themeColor: [
70+
{ media: "(prefers-color-scheme: light)", color: "white" },
71+
{ media: "(prefers-color-scheme: dark)", color: "black" },
72+
],
73+
};
74+
75+
export const metadata: Metadata = {
76+
title: {
77+
default: siteConfig.name,
78+
template: `%s | ${siteConfig.name}`,
79+
},
80+
description: siteConfig.description,
81+
openGraph: {
82+
type: "website",
83+
locale: "en_US",
84+
url: siteConfig.url,
85+
title: siteConfig.name,
86+
description: siteConfig.description,
87+
siteName: siteConfig.name,
88+
},
89+
twitter: {
90+
card: "summary_large_image",
91+
title: siteConfig.name,
92+
description: siteConfig.description,
93+
creator: siteConfig.author.x,
94+
},
95+
icons: {
96+
icon: [
97+
{ url: "/favicon.ico", sizes: "32x32" },
98+
{ url: "/favicon-16x16.png", type: "image/png", sizes: "16x16" },
99+
{ url: "/favicon-32x32.png", type: "image/png", sizes: "32x32" },
100+
{ url: "/icon-192.png", type: "image/png", sizes: "192x192" },
101+
{ url: "/icon-512.png", type: "image/png", sizes: "512x512" },
102+
],
103+
apple: [{ url: "/apple-icon.png", type: "image/png" }],
104+
},
105+
metadataBase: new URL(absoluteUrl("/")),
106+
};

src/components/details-header/details-header.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
import { getUser } from "@/lib/auth";
1616
import { getUserFavorites, getUserPlaylists } from "@/lib/db/queries";
1717
import { cn, formatDuration, getHref, getImageSrc } from "@/lib/utils";
18+
import { DownloadButton } from "../download-button";
1819
import { ImageWithFallback } from "../image-with-fallback";
1920
import { LikeButton } from "../like-button";
2021
import { PlayButton } from "../play-button";
@@ -253,6 +254,14 @@ export async function DetailsHeader({ item }: DetailsHeaderProps) {
253254
)}
254255
/>
255256

257+
<DownloadButton
258+
songs={songs ?? []}
259+
className={cn(
260+
buttonVariants({ size: "icon", variant: "outline" }),
261+
"rounded-full shadow-sm"
262+
)}
263+
/>
264+
256265
<MoreButton
257266
user={user}
258267
name={item.name}

0 commit comments

Comments
 (0)