diff --git a/.env b/.env index 05886e1..4d9441a 100644 --- a/.env +++ b/.env @@ -3,3 +3,4 @@ NEXT_PUBLIC_REDIRECT_URI="http://localhost:3000/auth/callback" NEXT_PUBLIC_AUTH_ENDPOINT="https://accounts.spotify.com/authorize" NEXT_PUBLIC_RESPONSE_TYPE="token" NEXT_PUBLIC_BACKEND_URL="http://localhost:3001" +NEXT_PUBLIC_FRONTENT_URL="localhost:3000" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 81956b7..00c1867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "unify", "version": "0.1.0", "dependencies": { + "@nivo/pie": "^0.85.1", "@nivo/radar": "^0.85.1", "@supabase/ssr": "^0.1.0", "@supabase/supabase-js": "^2.39.8", @@ -1495,6 +1496,21 @@ "node": ">= 10" } }, + "node_modules/@nivo/arcs": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.85.1.tgz", + "integrity": "sha512-UwwiSXHWY8cIgi3FADQJX8gyFCJfdx1N80MzxFGuHOYbTcBmsRMMbZYfqXJ5z/x61ulTkLcv/yVvlTEOCKMlcQ==", + "dependencies": { + "@nivo/colors": "0.85.1", + "@nivo/core": "0.85.1", + "@react-spring/web": "9.4.5 || ^9.7.2", + "@types/d3-shape": "^2.0.0", + "d3-shape": "^1.3.5" + }, + "peerDependencies": { + "react": ">= 16.14.0 < 19.0.0" + } + }, "node_modules/@nivo/colors": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.85.1.tgz", @@ -1558,6 +1574,23 @@ "react": ">= 16.14.0 < 19.0.0" } }, + "node_modules/@nivo/pie": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.85.1.tgz", + "integrity": "sha512-2dSQ7YIc6BLkYFadg+r6uOR5FXOCRSCWAYEIlvMapAvYqQ6/ie3ZnMtEB9idiucy8F4I/zF5C08OSr2jE4DJ9g==", + "dependencies": { + "@nivo/arcs": "0.85.1", + "@nivo/colors": "0.85.1", + "@nivo/core": "0.85.1", + "@nivo/legends": "0.85.1", + "@nivo/tooltip": "0.85.1", + "@types/d3-shape": "^2.0.0", + "d3-shape": "^1.3.5" + }, + "peerDependencies": { + "react": ">= 16.14.0 < 19.0.0" + } + }, "node_modules/@nivo/radar": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@nivo/radar/-/radar-0.85.1.tgz", diff --git a/package.json b/package.json index 963c3fc..8168531 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "prepare": "husky install" }, "dependencies": { + "@nivo/pie": "^0.85.1", "@nivo/radar": "^0.85.1", "@supabase/ssr": "^0.1.0", "@supabase/supabase-js": "^2.39.8", diff --git a/src/app/Index.jsx b/src/app/Index.jsx index f4b01da..9b3ae11 100644 --- a/src/app/Index.jsx +++ b/src/app/Index.jsx @@ -3,54 +3,35 @@ import { useEffect, useState } from "react"; import IndexContent from "@/components/svg-art/index_content"; - -const CLIENT_ID = process.env.NEXT_PUBLIC_CLIENT_ID; -const REDIRECT_URI = process.env.NEXT_PUBLIC_REDIRECT_URI; -const AUTH_ENDPOINT = process.env.NEXT_PUBLIC_AUTH_ENDPOINT; -const RESPONSE_TYPE = process.env.NEXT_PUBLIC_RESPONSE_TYPE; +import createClient from "@/utils/supabase/client"; +import { loginWithSpotify } from "./login/actions"; export default function HomePage() { - const [token, setToken] = useState(null); - - const handleLogin = () => { - const params = new URLSearchParams(); - params.append("client_id", CLIENT_ID); - params.append("response_type", RESPONSE_TYPE); - params.append("redirect_uri", REDIRECT_URI); - params.append( - "scope", - "user-read-private user-read-email user-library-read user-follow-read user-top-read user-modify-playback-state", - ); - - const url = `${AUTH_ENDPOINT}?${params.toString()}`; - - // Open Spotify login in same window, will redirect back - window.open(url, "_self"); - }; - - const enterCode = () => { - // console.log("enter code"); - }; - - const handleTokenFromCallback = () => { - // Extract the token from the URL hash - const urlParams = new URLSearchParams(window.location.hash.substr(1)); - const newToken = urlParams.get("access_token"); - - if (newToken) { - setToken(newToken); - window.localStorage.setItem("token", newToken); - } - }; + const supabase = createClient(); - // Check for token in the URL hash when component mounts + // check if user is already logged in useEffect(() => { - handleTokenFromCallback(); + (async () => { + // console.log("use effect running"); + const { + data: { user }, + } = await supabase.auth.getUser(); + // console.log("user: ", user); + if (user) { + // already logged in + // router.replace("/account"); + // console.log("user is logged in"); + } else { + // console.log("user is not logged in"); + } + })().catch((err) => { + console.error(err); // TODO display error message to user + }); }, []); return (
- +
); } diff --git a/src/app/auth/callback/route.js b/src/app/auth/callback/route.js index b226e28..fe72327 100644 --- a/src/app/auth/callback/route.js +++ b/src/app/auth/callback/route.js @@ -10,6 +10,8 @@ export async function GET(request) { const redirectTo = request.nextUrl.clone(); redirectTo.searchParams.delete("code"); + // console.log("code: ", code); + if (code) { const supabase = createClient(); @@ -37,10 +39,17 @@ export async function GET(request) { return NextResponse.redirect(redirectTo); } + // console.log("spotify user data: ", spotifyUserData); + + // console.log("username: ", spotifyUserData.userProfile.id); + // update DB with Spotify username + Spotify data const { dbError } = await supabase .from("profiles") - .update({ username: spotifyUserData.id, spotify_data: spotifyUserData }) + .update({ + username: spotifyUserData.userProfile.id, + spotify_data: spotifyUserData, + }) .eq("id", data.user.id); if (dbError) { @@ -50,7 +59,10 @@ export async function GET(request) { } // once finished, redirect user to account page - redirectTo.pathname = "/account"; + redirectTo.pathname = `/user/${spotifyUserData.userProfile.id}`; return NextResponse.redirect(redirectTo); } + + redirectTo.pathname = "/"; + return NextResponse.redirect(redirectTo); } diff --git a/src/app/globals.css b/src/app/globals.css index 867624e..e78ae48 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -9,3 +9,36 @@ font-weight: 400; font-style: normal; } + +.circle-row { + display: flex; + justify-content: center; +} + +.circle { + width: 300px; + height: 300px; + border-radius: 50%; + background-color: #d1d5db; + display: flex; + justify-content: center; + align-items: center; + margin: 0 10px; + flex-direction: column; +} + +.circle:nth-child(2) { + margin-top: -40px; /* Adjust as needed */ +} + +.circle-text-large { + font-size: 58px; + color: black; + font-family: "Koulen", sans-serif; +} + +.circle-text-small { + font-size: 36px; + color: black; + font-family: "Koulen", sans-serif; +} diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 3886f01..5765047 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -8,7 +8,7 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import createClient from "@/utils/supabase/client"; -import { login, loginWithSpotify, signup } from "./actions"; +import { loginWithSpotify } from "./actions"; export default function LoginPage() { const router = useRouter(); @@ -37,35 +37,13 @@ export default function LoginPage() { return ( !loading && ( - <> -
- - - - - - -
+

- NOTE: You will need to confirm your email after signing up! If not, - you will not be able to login. +

- -
-
-
-
-

- -

-
- +
) ); } diff --git a/src/app/unify/[users]/page.jsx b/src/app/unify/[users]/page.jsx new file mode 100644 index 0000000..84fc418 --- /dev/null +++ b/src/app/unify/[users]/page.jsx @@ -0,0 +1,128 @@ +"use client"; + +import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import createClient from "@/utils/supabase/client"; +import UnifyContent from "@/components/UnifyContent"; + +export default function UnifyPage({ params: { users } }) { + // console.log(users); + + const [user1, user2] = users.split("%26"); + + const supabase = createClient(); + + const [loading, setLoading] = useState(true); + const [user1Data, setUser1Data] = useState(null); + const [user2Data, setUser2Data] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + if (!users.includes("%26")) { + // console.log("user: ", users); + + // Get current user's information from Supabase + const { data: currentUser, error } = await supabase.auth.getUser(); + + if (error) { + throw error; + } + + // console.log(currentUser); + + // console.log("id: ", currentUser.user.id); + + supabase + .from("profiles") + .select("username") + .eq("id", currentUser.user.id) + .then(({ data, error2 }) => { + if (error2) { + // TODO + console.error(error2); // TODO display error message to user + } + + // console.log(data); + + if (data && data.length > 0) { + // Concatenate paramValue with currentUser's ID + const redirectURL = `${users}&${data[0].username}`; + + // console.log(redirectURL); + + // Redirect to the generated URL + window.location.href = redirectURL; + } + }); + } + } catch (error) { + console.error("Error fetching data:", error.message); + } + }; + + fetchData(); + + // Cleanup function if necessary + return () => { + // Cleanup code if needed + }; + }, []); // Empty dependency array ensures useEffect runs only once + + useEffect(() => { + // find user by username (given as URL slug) in DB + supabase + .from("profiles") + .select("spotify_data") + .eq("username", user1) + .then(({ data, error }) => { + if (error) { + // TODO + console.error(error); // TODO display error message to user + } + + if (data && data.length > 0) { + setUser1Data(data[0].spotify_data); + } + + setLoading(false); + }); + }, []); + + useEffect(() => { + // find user by username (given as URL slug) in DB + supabase + .from("profiles") + .select("spotify_data") + .eq("username", user2) + .then(({ data, error }) => { + if (error) { + // TODO + console.error(error); // TODO display error message to user + } + + if (data && data.length > 0) { + setUser2Data(data[0].spotify_data); + } + + setLoading(false); + }); + }, []); + + return ( +
+ {!loading && user1Data && user2Data && ( +
+ +
+ )} + {!loading && (!user1Data || !user2Data) &&
User not found!
} +
+ ); +} + +UnifyPage.propTypes = { + params: PropTypes.shape({ + users: PropTypes.string.isRequired, + }).isRequired, +}; diff --git a/src/app/user/[slug]/page.jsx b/src/app/user/[slug]/page.jsx index bf7cc35..443d31e 100644 --- a/src/app/user/[slug]/page.jsx +++ b/src/app/user/[slug]/page.jsx @@ -2,7 +2,10 @@ import { useEffect, useState } from "react"; import PropTypes from "prop-types"; +import ReactDOMServer from "react-dom/server"; import createClient from "@/utils/supabase/client"; +import UserContent from "@/components/svg-art/user_content"; +import ShareCassette from "@/components/svg-art/share_cassette"; export default function UserPage({ params: { slug } }) { const supabase = createClient(); @@ -10,6 +13,84 @@ export default function UserPage({ params: { slug } }) { const [loading, setLoading] = useState(true); const [userData, setUserData] = useState(null); + // Function to handle sharing + const shareCassette = async () => { + // console.log("sharing song"); + + // Use Web Share API to share the default image + const svgString = ReactDOMServer.renderToString(); + // console.log(svgString); + + const img = new Image(); + + // Set the source of the image + img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`; + + // Wait for the image to load + img.onload = () => { + // Create a canvas element + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + if (!ctx) { + console.error("Unable to obtain 2D context for canvas."); + return; + } + + canvas.width = img.width; + canvas.height = img.height; + + // Clear canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Add canvas to the document body for debugging + // document.body.appendChild(canvas); + + canvas.width = img.width; + canvas.height = img.height; + + // Draw the image on the canvas + canvas.getContext("2d")?.drawImage(img, 0, 0); + + ctx.textAlign = "center"; + + // Render the text onto the canvas + ctx.font = "20px Koulen"; + ctx.fillStyle = "black"; + ctx.fillText( + `@${userData.userProfile.display_name}`, + canvas.width / 2, + 389, + ); + + // Convert canvas to blob + canvas.toBlob((blob) => { + // console.log(blob); + + if (navigator.share) { + // console.log("Web share API supported"); + navigator + .share({ + title: "Unify with me!", + text: `Compare our stats on Uni.fy`, + url: `http://${process.env.NEXT_PUBLIC_FRONTENT_URL}/unify/${userData.userProfile.id}`, + files: [ + new File([blob], "file.png", { + type: blob.type, + }), + ], + }) + .then(() => { + // console.log("Shared successfully"); + }) + .catch((error) => console.error("Error sharing:", error)); + } else { + // console.log("Web Share API not supported"); + } + }, "image/png"); + }; + }; + useEffect(() => { // find user by username (given as URL slug) in DB supabase @@ -23,6 +104,7 @@ export default function UserPage({ params: { slug } }) { } if (data && data.length > 0) { + // console.log("topArtists: ", data[0].spotify_data.topArtists); setUserData(data[0].spotify_data); } @@ -31,8 +113,15 @@ export default function UserPage({ params: { slug } }) { }, []); return (
-

User Page

- {!loading && userData &&
{JSON.stringify(userData)}
} + {!loading && userData && ( +
+ +
+ )} {!loading && !userData &&
User not found!
}
); diff --git a/src/components/UnifyContent.jsx b/src/components/UnifyContent.jsx new file mode 100644 index 0000000..51ebbb4 --- /dev/null +++ b/src/components/UnifyContent.jsx @@ -0,0 +1,347 @@ +import { ResponsiveRadar } from "@nivo/radar"; +import { ResponsivePie } from "@nivo/pie"; +import "@/app/globals.css"; +import ReactDOMServer from "react-dom/server"; +import ShareUnify from "@/components/svg-art/share_unify"; + +function calculateSimilarity(list1, list2) { + const intersection = Object.keys(list1).filter((key) => + Object.prototype.hasOwnProperty.call(list2, key), + ).length; + const union = Object.keys({ ...list1, ...list2 }).length; + const similarity = intersection / union; + return similarity * 100; // Convert to percentage +} + +function UnifyContent({ user1Data, user2Data }) { + // console.log(user1Data.featuresData); + // console.log(user2Data.featuresData); + + // Function to handle sharing + const shareUnify = async () => { + // console.log("sharing song"); + + // Use Web Share API to share the default image + const svgString = ReactDOMServer.renderToString(); + // console.log(svgString); + + const img = new Image(); + + // Set the source of the image + img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`; + + // Wait for the image to load + img.onload = () => { + // Create a canvas element + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + if (!ctx) { + console.error("Unable to obtain 2D context for canvas."); + return; + } + + canvas.width = img.width; + canvas.height = img.height; + + // Clear canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Add canvas to the document body for debugging + // document.body.appendChild(canvas); + + canvas.width = img.width; + canvas.height = img.height; + + // Draw the image on the canvas + canvas.getContext("2d")?.drawImage(img, 0, 0); + + ctx.textAlign = "center"; + + // Render the text onto the canvas + ctx.font = "20px Koulen"; + ctx.fillStyle = "black"; + ctx.fillText( + `@${user2Data.userProfile.display_name}`, + canvas.width / 2, + 302, + ); + ctx.fillText( + `@${user1Data.userProfile.display_name}`, + canvas.width / 2, + 501, + ); + + // Convert canvas to blob + canvas.toBlob((blob) => { + // console.log(blob); + + if (navigator.share) { + // console.log("Web share API supported"); + navigator + .share({ + title: "Unify with me!", + text: `Compare our stats on Uni.fy`, + url: "unify", + files: [ + new File([blob], "file.png", { + type: blob.type, + }), + ], + }) + .then(() => { + // console.log("Shared successfully"); + }) + .catch((error) => console.error("Error sharing:", error)); + } else { + // console.log("Web Share API not supported"); + } + }, "image/png"); + }; + }; + + const user1Name = user1Data.userProfile.display_name; + const user2Name = user2Data.userProfile.display_name; + + const map = {}; + user1Data.featuresData.forEach((item) => { + map[item.feature] = item.value; + }); + + const combinedFeaturesData = user2Data.featuresData.map((item) => { + const userData = {}; + userData[user1Name] = + map[item.feature] !== undefined ? map[item.feature] : undefined; + userData[user2Name] = item.value; + userData.feature = item.feature; + return userData; + }); + + const user1topGenres = Object.entries(user1Data.topGenres) + .sort((a, b) => b[1] - a[1]) // Sort genres by frequency in descending order + .slice(0, 5) // Get the top 5 genres + .map(([id, value]) => ({ id, value })); // Map to { id: genre, value: frequency } objects + + const user2topGenres = Object.entries(user2Data.topGenres) + .sort((a, b) => b[1] - a[1]) // Sort genres by frequency in descending order + .slice(0, 5) // Get the top 5 genres + .map(([id, value]) => ({ id, value })); // Map to { id: genre, value: frequency } objects + + const genreSimilarity = calculateSimilarity( + user1Data.topGenres, + user2Data.topGenres, + ); + + // console.log("combinedFeaturesData: ", combinedFeaturesData); + + return ( + <> +
+ {" "} + 0% Match! +
+
+ {/* */} +
+

+ @{user1Data.userProfile.display_name} +

+
+
+

+ @{user2Data.userProfile.display_name} +

+
+
+
+
+ +5% + Same Top Artist +
+
+ {genreSimilarity}% + Genres Shared +
+
+ +10% + Same Top Song +
+
+
+ +
+
+
+
+ Top Artists: +
+ {user1Data.topArtists.map((artist) => ( +
{artist.name}
+ ))} +
+
+
+
+ Top Artists: +
+ {user2Data.topArtists.map((artist) => ( +
{artist.name}
+ ))} +
+
+ {user1topGenres ? ( +
+ +
+ ) : ( +
Loading...
+ )} + {user2topGenres ? ( +
+ +
+ ) : ( +
Loading...
+ )} +
+
+ Top Songs: +
+ {user1Data.topSongs.map((song) => ( +
{song.name}
+ ))} +
+
+
+
+ Top Songs: +
+ {user2Data.topSongs.map((song) => ( +
{song.name}
+ ))} +
+
+
+
+ {combinedFeaturesData ? ( +
+ +
+ ) : ( +
Loading...
+ )} +
+ + ); +} +export default UnifyContent; diff --git a/src/components/svg-art/index_content.jsx b/src/components/svg-art/index_content.jsx index 0565582..fbed7d2 100644 --- a/src/components/svg-art/index_content.jsx +++ b/src/components/svg-art/index_content.jsx @@ -1,4 +1,6 @@ -export default function IndexContent({ handleLogin, enterCode }) { +import { loginWithSpotify } from "@/app/login/actions"; + +export default function IndexContent() { return ( loginWithSpotify()} style={{ cursor: "pointer" }} /> loginWithSpotify()} style={{ cursor: "pointer" }} d="M985.075 293H975.378C975.3 293 975.261 292.951 975.261 292.854L975.29 272.272C975.29 272.175 975.339 272.126 975.437 272.126H978.776C978.874 272.126 978.923 272.175 978.923 272.272L978.894 289.309H985.075C985.173 289.309 985.222 289.357 985.222 289.455V292.854C985.222 292.951 985.173 293 985.075 293ZM992.736 293.293C991.672 293.293 990.69 293.02 989.792 292.473C988.903 291.926 988.186 291.198 987.639 290.29C987.102 289.372 986.833 288.366 986.833 287.272L986.862 277.766C986.862 276.652 987.131 275.651 987.668 274.763C988.195 273.864 988.908 273.146 989.807 272.609C990.705 272.062 991.682 271.789 992.736 271.789C993.83 271.789 994.812 272.058 995.681 272.595C996.56 273.132 997.263 273.854 997.79 274.763C998.327 275.661 998.596 276.662 998.596 277.766L998.625 287.272C998.625 288.366 998.361 289.367 997.834 290.275C997.297 291.193 996.584 291.926 995.695 292.473C994.807 293.02 993.82 293.293 992.736 293.293ZM992.736 289.675C993.342 289.675 993.869 289.431 994.318 288.942C994.768 288.444 994.992 287.888 994.992 287.272L994.963 277.766C994.963 277.102 994.753 276.54 994.333 276.081C993.913 275.622 993.381 275.393 992.736 275.393C992.121 275.393 991.594 275.617 991.154 276.066C990.715 276.516 990.495 277.082 990.495 277.766V287.272C990.495 287.927 990.715 288.493 991.154 288.972C991.594 289.44 992.121 289.675 992.736 289.675ZM1006.39 293.293C1005.3 293.293 1004.33 293.024 1003.46 292.487C1002.58 291.95 1001.87 291.228 1001.33 290.319C1000.81 289.411 1000.54 288.396 1000.54 287.272V277.766C1000.54 276.672 1000.81 275.676 1001.35 274.777C1001.88 273.869 1002.58 273.146 1003.47 272.609C1004.36 272.062 1005.33 271.789 1006.39 271.789C1007.49 271.789 1008.49 272.053 1009.38 272.58C1010.27 273.117 1010.97 273.835 1011.49 274.733C1012.01 275.632 1012.28 276.643 1012.28 277.766V279.143C1012.28 279.221 1012.23 279.26 1012.13 279.26H1008.76C1008.68 279.26 1008.64 279.221 1008.64 279.143V278C1008.64 277.238 1008.43 276.608 1008 276.11C1007.57 275.612 1007.03 275.363 1006.39 275.363C1005.81 275.363 1005.3 275.603 1004.85 276.081C1004.4 276.56 1004.18 277.121 1004.18 277.766V287.272C1004.18 287.927 1004.4 288.493 1004.84 288.972C1005.28 289.44 1005.79 289.675 1006.39 289.675C1007.03 289.675 1007.57 289.445 1008 288.986C1008.43 288.518 1008.64 287.946 1008.64 287.272V285.793H1006.55C1006.44 285.793 1006.39 285.744 1006.39 285.646V282.292C1006.39 282.194 1006.44 282.146 1006.55 282.146H1012.16C1012.24 282.146 1012.28 282.194 1012.28 282.292V287.272C1012.28 288.396 1012.01 289.411 1011.49 290.319C1010.95 291.228 1010.24 291.95 1009.36 292.487C1008.48 293.024 1007.49 293.293 1006.39 293.293ZM1025.74 293H1022.34C1022.24 293 1022.19 292.951 1022.19 292.854L1022.22 272.243C1022.22 272.165 1022.26 272.126 1022.34 272.126H1025.71C1025.79 272.126 1025.83 272.165 1025.83 272.243L1025.86 292.854C1025.86 292.951 1025.82 293 1025.74 293ZM1031.76 293H1028.76C1028.6 293 1028.52 292.932 1028.52 292.795L1028.49 272.36C1028.49 272.204 1028.57 272.126 1028.73 272.126H1031.44L1036.51 283.947L1036.36 272.36C1036.36 272.204 1036.45 272.126 1036.62 272.126H1039.6C1039.71 272.126 1039.77 272.204 1039.77 272.36L1039.8 292.824C1039.8 292.941 1039.75 293 1039.66 293H1037.02L1031.82 281.955L1032.04 292.766C1032.04 292.922 1031.95 293 1031.76 293ZM1056.6 293H1052.97C1052.88 293 1052.83 292.951 1052.82 292.854L1049.28 272.243C1049.26 272.165 1049.29 272.126 1049.37 272.126H1052.74C1052.81 272.126 1052.86 272.165 1052.88 272.243L1054.8 285.793L1056.78 272.243C1056.8 272.165 1056.86 272.126 1056.96 272.126H1059.67C1059.74 272.126 1059.79 272.165 1059.81 272.243L1061.76 285.793L1063.71 272.243C1063.73 272.165 1063.78 272.126 1063.85 272.126H1067.19C1067.29 272.126 1067.33 272.165 1067.31 272.243L1063.74 292.854C1063.73 292.893 1063.7 292.927 1063.65 292.956L1063.59 293H1059.96C1059.9 293 1059.85 292.951 1059.81 292.854L1058.27 282.087L1056.75 292.854C1056.74 292.951 1056.69 293 1056.6 293ZM1072.61 293H1069.22C1069.12 293 1069.07 292.951 1069.07 292.854L1069.1 272.243C1069.1 272.165 1069.14 272.126 1069.22 272.126H1072.58C1072.66 272.126 1072.7 272.165 1072.7 272.243L1072.73 292.854C1072.73 292.951 1072.69 293 1072.61 293ZM1081.77 293H1078.39C1078.3 293 1078.25 292.951 1078.25 292.854V275.744H1074.36C1074.26 275.744 1074.21 275.695 1074.21 275.598L1074.24 272.243C1074.24 272.165 1074.28 272.126 1074.36 272.126H1085.75C1085.86 272.126 1085.92 272.165 1085.92 272.243V275.598C1085.92 275.695 1085.88 275.744 1085.8 275.744H1081.86L1081.89 292.854C1081.89 292.951 1081.85 293 1081.77 293ZM1090.9 293H1087.5C1087.42 293 1087.38 292.951 1087.38 292.854L1087.41 272.243C1087.41 272.165 1087.46 272.126 1087.56 272.126H1090.9C1090.99 272.126 1091.04 272.165 1091.04 272.243L1091.01 280.402H1095.51V272.243C1095.51 272.165 1095.55 272.126 1095.63 272.126H1098.97C1099.06 272.126 1099.11 272.165 1099.11 272.243L1099.17 292.854C1099.17 292.951 1099.12 293 1099.03 293H1095.66C1095.56 293 1095.51 292.951 1095.51 292.854V284.035H1091.01V292.854C1091.01 292.951 1090.97 293 1090.9 293ZM1114.51 293.293C1113.44 293.293 1112.46 293.02 1111.56 292.473C1110.68 291.926 1109.96 291.198 1109.43 290.29C1108.9 289.372 1108.63 288.366 1108.63 287.272V285.896C1108.63 285.788 1108.68 285.734 1108.78 285.734H1112.15C1112.23 285.734 1112.27 285.788 1112.27 285.896V287.272C1112.27 287.927 1112.49 288.493 1112.93 288.972C1113.37 289.44 1113.89 289.675 1114.51 289.675C1115.13 289.675 1115.67 289.436 1116.11 288.957C1116.54 288.469 1116.76 287.907 1116.76 287.272C1116.76 286.54 1116.29 285.9 1115.33 285.354C1115.17 285.256 1114.97 285.139 1114.71 285.002C1114.47 284.855 1114.18 284.689 1113.83 284.504C1113.49 284.318 1113.16 284.138 1112.84 283.962C1112.52 283.776 1112.2 283.601 1111.9 283.435C1110.81 282.79 1109.99 281.984 1109.46 281.018C1108.93 280.041 1108.66 278.947 1108.66 277.736C1108.66 276.623 1108.94 275.617 1109.48 274.719C1110.03 273.83 1110.74 273.127 1111.62 272.609C1112.51 272.082 1113.47 271.818 1114.51 271.818C1115.57 271.818 1116.55 272.082 1117.44 272.609C1118.33 273.146 1119.04 273.859 1119.56 274.748C1120.1 275.637 1120.37 276.633 1120.37 277.736V280.197C1120.37 280.275 1120.33 280.314 1120.25 280.314H1116.88C1116.8 280.314 1116.76 280.275 1116.76 280.197L1116.74 277.736C1116.74 277.033 1116.52 276.462 1116.08 276.022C1115.64 275.583 1115.11 275.363 1114.51 275.363C1113.89 275.363 1113.37 275.598 1112.93 276.066C1112.49 276.535 1112.27 277.092 1112.27 277.736C1112.27 278.391 1112.4 278.938 1112.68 279.377C1112.96 279.816 1113.47 280.236 1114.22 280.637C1114.29 280.676 1114.47 280.773 1114.76 280.93C1115.04 281.086 1115.35 281.262 1115.7 281.457C1116.05 281.643 1116.36 281.813 1116.65 281.97C1116.93 282.116 1117.1 282.204 1117.16 282.233C1118.16 282.79 1118.94 283.474 1119.52 284.284C1120.1 285.095 1120.4 286.091 1120.4 287.272C1120.4 288.415 1120.13 289.44 1119.61 290.349C1119.07 291.257 1118.36 291.975 1117.47 292.502C1116.58 293.029 1115.59 293.293 1114.51 293.293ZM1125.99 293H1122.62C1122.53 293 1122.48 292.951 1122.48 292.854L1122.54 272.243C1122.54 272.165 1122.58 272.126 1122.65 272.126H1128.42C1130.26 272.126 1131.71 272.688 1132.78 273.811C1133.85 274.924 1134.39 276.442 1134.39 278.366C1134.39 279.772 1134.11 280.998 1133.55 282.043C1132.99 283.078 1132.25 283.879 1131.35 284.445C1130.46 285.012 1129.48 285.295 1128.42 285.295H1126.14V292.854C1126.14 292.951 1126.09 293 1125.99 293ZM1128.42 275.686L1126.14 275.715V281.662H1128.42C1129.06 281.662 1129.61 281.359 1130.08 280.754C1130.55 280.139 1130.78 279.343 1130.78 278.366C1130.78 277.585 1130.57 276.945 1130.15 276.447C1129.73 275.939 1129.16 275.686 1128.42 275.686ZM1141.97 293.293C1140.91 293.293 1139.93 293.02 1139.03 292.473C1138.14 291.926 1137.42 291.198 1136.88 290.29C1136.34 289.372 1136.07 288.366 1136.07 287.272L1136.1 277.766C1136.1 276.652 1136.37 275.651 1136.91 274.763C1137.43 273.864 1138.15 273.146 1139.04 272.609C1139.94 272.062 1140.92 271.789 1141.97 271.789C1143.07 271.789 1144.05 272.058 1144.92 272.595C1145.8 273.132 1146.5 273.854 1147.03 274.763C1147.57 275.661 1147.83 276.662 1147.83 277.766L1147.86 287.272C1147.86 288.366 1147.6 289.367 1147.07 290.275C1146.54 291.193 1145.82 291.926 1144.93 292.473C1144.04 293.02 1143.06 293.293 1141.97 293.293ZM1141.97 289.675C1142.58 289.675 1143.11 289.431 1143.56 288.942C1144.01 288.444 1144.23 287.888 1144.23 287.272L1144.2 277.766C1144.2 277.102 1143.99 276.54 1143.57 276.081C1143.15 275.622 1142.62 275.393 1141.97 275.393C1141.36 275.393 1140.83 275.617 1140.39 276.066C1139.95 276.516 1139.73 277.082 1139.73 277.766V287.272C1139.73 287.927 1139.95 288.493 1140.39 288.972C1140.83 289.44 1141.36 289.675 1141.97 289.675ZM1156.54 293H1153.15C1153.06 293 1153.02 292.951 1153.02 292.854V275.744H1149.12C1149.03 275.744 1148.98 275.695 1148.98 275.598L1149.01 272.243C1149.01 272.165 1149.04 272.126 1149.12 272.126H1160.52C1160.63 272.126 1160.68 272.165 1160.68 272.243V275.598C1160.68 275.695 1160.64 275.744 1160.56 275.744H1156.62L1156.65 292.854C1156.65 292.951 1156.61 293 1156.54 293ZM1165.69 293H1162.29C1162.19 293 1162.15 292.951 1162.15 292.854L1162.17 272.243C1162.17 272.165 1162.21 272.126 1162.29 272.126H1165.66C1165.74 272.126 1165.78 272.165 1165.78 272.243L1165.81 292.854C1165.81 292.951 1165.77 293 1165.69 293ZM1171.96 293H1168.59C1168.49 293 1168.44 292.951 1168.44 292.854L1168.5 272.243C1168.5 272.165 1168.54 272.126 1168.62 272.126H1178.23C1178.33 272.126 1178.38 272.165 1178.38 272.243V275.627C1178.38 275.705 1178.34 275.744 1178.26 275.744H1172.11V280.402H1178.26C1178.34 280.402 1178.38 280.451 1178.38 280.549L1178.41 283.947C1178.41 284.025 1178.36 284.064 1178.26 284.064H1172.11V292.854C1172.11 292.951 1172.06 293 1171.96 293ZM1187.33 293H1183.88C1183.82 293 1183.78 292.961 1183.78 292.883L1183.81 284.357L1179.8 272.243C1179.78 272.165 1179.81 272.126 1179.88 272.126H1183.22C1183.32 272.126 1183.38 272.165 1183.4 272.243L1185.61 280.314L1187.87 272.243C1187.89 272.165 1187.94 272.126 1188.01 272.126H1191.38C1191.46 272.126 1191.49 272.165 1191.47 272.243L1187.41 284.24L1187.44 292.883C1187.44 292.961 1187.4 293 1187.33 293Z" fill="black" /> - - @{displayName} - diff --git a/src/components/svg-art/share_unify.jsx b/src/components/svg-art/share_unify.jsx new file mode 100644 index 0000000..90ec980 --- /dev/null +++ b/src/components/svg-art/share_unify.jsx @@ -0,0 +1,233 @@ +function ShareUnify() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} +export default ShareUnify; diff --git a/src/components/svg-art/user_content.jsx b/src/components/svg-art/user_content.jsx index 8aced49..6d32663 100644 --- a/src/components/svg-art/user_content.jsx +++ b/src/components/svg-art/user_content.jsx @@ -1,7 +1,14 @@ import { ResponsiveRadar } from "@nivo/radar"; +import { ResponsivePie } from "@nivo/pie"; import "@/app/globals.css"; function UserContent({ userData, shareCassette, unify }) { + // Convert object to array of { id: genre, value: frequency } objects + const top5Genres = Object.entries(userData.topGenres) + .sort((a, b) => b[1] - a[1]) // Sort genres by frequency in descending order + .slice(0, 5) // Get the top 5 genres + .map(([id, value]) => ({ id, value })); // Map to { id: genre, value: frequency } objects + return (
{/*