Skip to content
Open
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_APP_ID=
VITE_THEAUDIODB_API_KEY=
Comment on lines +6 to +7
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Reorder env keys to satisfy dotenv-linter

dotenv-linter flags Line 6 because VITE_FIREBASE_APP_ID is expected before VITE_FIREBASE_AUTH_DOMAIN. Please reorder the entries to clear the warning.

 VITE_FIREBASE_API_KEY=
-VITE_FIREBASE_AUTH_DOMAIN=
+VITE_FIREBASE_APP_ID=
+VITE_FIREBASE_AUTH_DOMAIN=
 VITE_FIREBASE_PROJECT_ID=
 VITE_FIREBASE_STORAGE_BUCKET=
 VITE_FIREBASE_MESSAGING_SENDER_ID=
-VITE_FIREBASE_APP_ID=
 VITE_THEAUDIODB_API_KEY=

Based on static analysis hints

📝 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
VITE_FIREBASE_APP_ID=
VITE_THEAUDIODB_API_KEY=
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_THEAUDIODB_API_KEY=
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 6-6: [UnorderedKey] The VITE_FIREBASE_APP_ID key should go before the VITE_FIREBASE_AUTH_DOMAIN key

(UnorderedKey)

🤖 Prompt for AI Agents
.env.example around lines 6-7: dotenv-linter expects VITE_FIREBASE_APP_ID to
appear before VITE_FIREBASE_AUTH_DOMAIN, so move the VITE_FIREBASE_APP_ID line
so it comes immediately before the VITE_FIREBASE_AUTH_DOMAIN entry (and adjust
surrounding keys to maintain the linter's expected ordering), then save and
re-run the linter to confirm the warning is cleared.

32 changes: 31 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import globals from "globals";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import typescriptParser from "@typescript-eslint/parser";
import typescriptPlugin from "@typescript-eslint/eslint-plugin";

export default [
{
Expand All @@ -18,6 +20,7 @@ export default [
"public/",
],
},
// JavaScript and JSX files configuration
{
files: ["**/*.{js,jsx}"],
languageOptions: {
Expand All @@ -28,7 +31,7 @@ export default [
sourceType: "module",
},
},
settings: { react: { version: "18.3" } },
settings: { react: { version: "detect" } },
plugins: {
react,
"react-hooks": reactHooks,
Expand All @@ -39,8 +42,35 @@ export default [
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
"react/prop-types": "off",
"react/jsx-no-target-blank": "off",
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
},
// TypeScript and TSX files configuration
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
globals: globals.browser,
parser: typescriptParser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
settings: { react: { version: "detect" } },
plugins: {
"@typescript-eslint": typescriptPlugin,
react,
"react-hooks": reactHooks,
},
rules: {
...typescriptPlugin.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
"react/prop-types": "off",
},
},
];
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"prepare": "husky"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

Verify that the dependency is used in the codebase.

The @google/generative-ai dependency is added but doesn't appear to be imported or used in any of the files changed in this PR. The PR description mentions using TheAudioDB API for fetching artist biographies, not Google's Generative AI.

Run the following script to verify if this dependency is actually used:

If the dependency is unused, consider removing it to avoid bloating the bundle. If it's for future use, it should be added in a separate PR when actually needed.


🏁 Script executed:

#!/bin/bash
# Search for imports of @google/generative-ai in the codebase
rg -n --type=ts --type=js --type=jsx -C3 '@google/generative-ai|google/generative-ai'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for any import or require of @google/generative-ai in JS/TS files
rg -n '@google/generative-ai' -C3 --glob '*.{js,jsx,ts,tsx}'

Length of output: 0


Remove unused dependency @google/generative-ai
This package isn’t imported or used in any code; remove it from package.json (line 14) to avoid unnecessary bundle bloat.

🤖 Prompt for AI Agents
package.json around line 14: the dependency "@google/generative-ai": "^0.24.1"
is unused; remove that dependency entry from package.json and then run npm/yarn
install (or npm prune) to update lockfile and node_modules so the package is no
longer installed.

"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-icons": "^1.3.2",
Expand Down Expand Up @@ -50,6 +51,8 @@
"@types/node": "^22.10.2",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
Expand Down
21 changes: 17 additions & 4 deletions src/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ Api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
toast.error(`Error: ${error.response.status} - ${error.response.statusText}`);
console.error("API Error:", error.response.data);
} else if (error.request) {
// The request was made but no response was received
toast.error("Error: No response from server. Please check your internet connection.");
console.error("API Error: No response received", error.request);
} else {
// Something happened in setting up the request that triggered an Error
toast.error("Error: Something went wrong with the request.");
console.error("API Error:", error.message);
}
Expand All @@ -42,6 +38,7 @@ import {
setDoc,
} from "firebase/firestore";
import { app, db } from "./Auth/firebase";

export const fetchFireStore = (setPlaylist, setLikedSongs) => {
let auth = getAuth(app);
onAuthStateChanged(auth, async (user) => {
Expand Down Expand Up @@ -229,3 +226,19 @@ export async function fetchSongsByIds(songIds) {
return { success: false, data: [] };
}
}

export async function fetchArtistBio(artistName) {
try {
const apiKey = import.meta.env.VITE_THEAUDIODB_API_KEY;
const url = `https://www.theaudiodb.com/api/v1/json/${apiKey}/search.php?s=${artistName}`;
const response = await axios.get(url);

if (response.data && response.data.artists) {
return response.data.artists[0].strBiographyEN;
}
return null;
} catch (error) {
console.error("Error fetching artist biography:", error);
return null;
}
}
Comment on lines +230 to +244
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add URL encoding and array safety checks.

The function has several issues that could cause runtime errors:

  1. Missing URL encoding: artistName is directly interpolated into the URL without encoding. Artist names containing spaces or special characters (e.g., "AC/DC", "Beyoncé") will result in malformed URLs and API failures.

  2. Unsafe array access: response.data.artists[0] accesses the array without verifying it exists or has elements, which will throw a TypeError if artists is null, undefined, or an empty array.

  3. Missing API key validation: The API key from import.meta.env.VITE_THEAUDIODB_API_KEY is used without validation, which could expose undefined in the URL if the environment variable is not set.

Apply this diff to fix these issues:

 export async function fetchArtistBio(artistName) {
   try {
     const apiKey = import.meta.env.VITE_THEAUDIODB_API_KEY;
+    if (!apiKey) {
+      console.error("VITE_THEAUDIODB_API_KEY is not configured");
+      return null;
+    }
-    const url = `https://www.theaudiodb.com/api/v1/json/${apiKey}/search.php?s=${artistName}`;
+    const encodedArtist = encodeURIComponent(artistName);
+    const url = `https://www.theaudiodb.com/api/v1/json/${apiKey}/search.php?s=${encodedArtist}`;
     const response = await axios.get(url);

-    if (response.data && response.data.artists) {
+    if (response.data?.artists && response.data.artists.length > 0) {
       return response.data.artists[0].strBiographyEN;
     }
     return null;
   } catch (error) {
     console.error("Error fetching artist biography:", error);
     return null;
   }
 }
📝 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
export async function fetchArtistBio(artistName) {
try {
const apiKey = import.meta.env.VITE_THEAUDIODB_API_KEY;
const url = `https://www.theaudiodb.com/api/v1/json/${apiKey}/search.php?s=${artistName}`;
const response = await axios.get(url);
if (response.data && response.data.artists) {
return response.data.artists[0].strBiographyEN;
}
return null;
} catch (error) {
console.error("Error fetching artist biography:", error);
return null;
}
}
export async function fetchArtistBio(artistName) {
try {
const apiKey = import.meta.env.VITE_THEAUDIODB_API_KEY;
if (!apiKey) {
console.error("VITE_THEAUDIODB_API_KEY is not configured");
return null;
}
const encodedArtist = encodeURIComponent(artistName);
const url = `https://www.theaudiodb.com/api/v1/json/${apiKey}/search.php?s=${encodedArtist}`;
const response = await axios.get(url);
if (response.data?.artists && response.data.artists.length > 0) {
return response.data.artists[0].strBiographyEN;
}
return null;
} catch (error) {
console.error("Error fetching artist biography:", error);
return null;
}
}
🤖 Prompt for AI Agents
In src/Api.js around lines 220 to 234, fix fetchArtistBio by URL-encoding
artistName with encodeURIComponent, validate the API key from
import.meta.env.VITE_THEAUDIODB_API_KEY and early-return null (or throw) if
missing, and defensively check that response.data.artists is an array with
length > 0 before accessing index 0; return the biography only when those checks
pass and otherwise return null, keeping the existing try/catch and logging
behavior.

39 changes: 39 additions & 0 deletions src/components/Artist/ArtistBio.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import PropTypes from "prop-types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

const ArtistBio = ({ artistData, bioText }) => {
// Use the fetched bioText. If it's not available, fall back to the placeholder.
const bio =
bioText ||
`A celebrated artist known for a unique blend of genres, ${artistData?.name} has captivated audiences worldwide with their soulful melodies and profound lyrics. Rising from humble beginnings, their passion for music has led them on a journey of sonic exploration, resulting in a discography that is both timeless and innovative.`;

return (
<div className="mt-8">
<Card>
<CardHeader>
<CardTitle>About {artistData?.name}</CardTitle>
</CardHeader>
<CardContent>
{/* The 'whitespace-pre-wrap' class helps preserve formatting like newlines from the API */}
<p className="text-muted-foreground whitespace-pre-wrap">{bio}</p>
</CardContent>
</Card>
</div>
);
};

// Add prop validation to satisfy the linter
ArtistBio.propTypes = {
artistData: PropTypes.shape({
name: PropTypes.string,
bio: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
text: PropTypes.string,
}),
]),
}),
bioText: PropTypes.string,
};

export default ArtistBio;
Loading