From 426ed946e7295177d3674d964d6749a7e463cbf8 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sun, 15 Sep 2024 22:53:28 +0000 Subject: [PATCH 01/10] feat: Add Donations Page --- frontend/css/donors-page.less | 75 ++++++++++++ frontend/css/main.less | 1 + frontend/js/src/components/Navbar.tsx | 3 + frontend/js/src/donors/Donors.tsx | 160 ++++++++++++++++++++++++++ frontend/js/src/routes/index.tsx | 7 ++ 5 files changed, 246 insertions(+) create mode 100644 frontend/css/donors-page.less create mode 100644 frontend/js/src/donors/Donors.tsx diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less new file mode 100644 index 0000000000..654cd3554b --- /dev/null +++ b/frontend/css/donors-page.less @@ -0,0 +1,75 @@ +.donor-card { + display: flex; + flex-direction: column; + justify-content: space-between; + border: 1px solid #e2e8f0; + padding: 0.5rem 1.5rem; + margin: 1.5rem 0; + @media (min-width: 640px) { + flex-direction: row; + align-items: center; + } + + .donor-info { + display: flex; + margin-bottom: 1rem; + flex-direction: column; + + @media (min-width: 640px) { + margin-bottom: 0; + } + + .donation-amount { + font-weight: 600; + font-size: 1.75rem; + margin-bottom: 0.5rem; + .donor-name { + font-weight: 300 !important; + &:hover, + &:visited { + color: #353070 !important; + } + } + } + + .donation-date { + display: flex; + color: #6b7280; + gap: 10px; + } + } + + .donor-stats { + display: flex; + flex-direction: column; + align-items: flex-end; + + .donation-amount { + font-weight: 600; + font-size: 1.75rem; + margin-bottom: 0.5rem; + } + + .recent-listens { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + + .listen-item { + display: flex; + align-items: center; + color: #6b7280; + background-color: #edf2f7; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + gap: 0.5rem; + font-weight: 300; + + &:hover, + &:visited { + color: #6b7280 !important; + } + } + } + } +} diff --git a/frontend/css/main.less b/frontend/css/main.less index 9a5730a94c..55952c38a8 100644 --- a/frontend/css/main.less +++ b/frontend/css/main.less @@ -38,6 +38,7 @@ @import "release-card.less"; @import "search.less"; @import "accordion.less"; +@import "donors-page.less"; @icon-font-path: "/static/fonts/"; diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index f22beed764..ce05b86dd4 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -97,6 +97,9 @@ function Navbar() { Explore + + Donors +
diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx new file mode 100644 index 0000000000..03a4883615 --- /dev/null +++ b/frontend/js/src/donors/Donors.tsx @@ -0,0 +1,160 @@ +import * as React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faCalendar, + faListAlt, + faMusic, +} from "@fortawesome/free-solid-svg-icons"; +import { Link, useLocation, useSearchParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import Pill from "../components/Pill"; +import { formatListenCount } from "../explore/fresh-releases/utils"; +import Pagination from "../common/Pagination"; +import { getObjectForURLSearchParams } from "../utils/utils"; +import { RouteQuery } from "../utils/Loader"; +import Loader from "../components/Loader"; + +type DonorLoaderData = { + data: { + id: number; + name: string; + donated_at: string; + donation: number; + currency: "usd" | "eur"; + musicbrainz_id: string; + is_listenbrainz_user: boolean; + listenCount: number; + playlistCount: number; + }[]; + totalPageCount: number; +}; + +function Donors() { + const location = useLocation(); + const [searchParams, setSearchParams] = useSearchParams(); + const searchParamsObj = getObjectForURLSearchParams(searchParams); + const currPageNoStr = searchParams.get("page") || "1"; + const currPageNo = parseInt(currPageNoStr, 10); + const sort = searchParams.get("sort") || "date"; + + const { data, isLoading } = useQuery( + RouteQuery( + ["donors", currPageNoStr, sort], + `${location.pathname}${location.search}` + ) + ); + + const { data: donors, totalPageCount = 1 } = data || {}; + + const handleClickPrevious = () => { + setSearchParams({ + ...searchParamsObj, + page: Math.max(currPageNo - 1, 1).toString(), + }); + }; + + const handleClickNext = () => { + setSearchParams({ + ...searchParamsObj, + page: Math.min(currPageNo + 1, totalPageCount).toString(), + }); + }; + + const handleSortBy = (sortBy: string) => { + if (sortBy === sort) { + return; + } + setSearchParams({ + page: "1", + sort: sortBy, + }); + }; + + return ( +
+

Donations

+
+ handleSortBy("date")} + > + Date + + handleSortBy("amount")} + > + Amount + +
+ + {donors?.map((donor) => ( +
+
+

+ {donor.musicbrainz_id}{" "} + {donor.musicbrainz_id ? ( + <> + ( + + {donor.musicbrainz_id} + + ) + + ) : null} +

+

+ +

+ Donation Date:{" "} + {new Date(donor.donated_at).toLocaleDateString()} +

+

+
+
+

+ {donor.currency === "usd" ? "$" : "€"} + {donor.donation} +

+ {donor.musicbrainz_id && donor.is_listenbrainz_user && ( +
+ {donor.listenCount && ( + + + {formatListenCount(donor.listenCount)} Listens + + )} + {donor.playlistCount && ( + + + {formatListenCount(donor.playlistCount)} Playlists + + )} +
+ )} +
+
+ ))} +
+ +
+ ); +} + +export default Donors; diff --git a/frontend/js/src/routes/index.tsx b/frontend/js/src/routes/index.tsx index cbea3b43cd..7008619ba7 100644 --- a/frontend/js/src/routes/index.tsx +++ b/frontend/js/src/routes/index.tsx @@ -170,6 +170,13 @@ const getIndexRoutes = (): RouteObject[] => { return { Component: APIAuth.default }; }, }, + { + path: "donors/", + lazy: async () => { + const Donors = await import("../donors/Donors"); + return { Component: Donors.default }; + }, + }, ], }, ]; From f59367a31dcc08930f42da65e2442861260a96ba Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sun, 15 Sep 2024 22:55:18 +0000 Subject: [PATCH 02/10] feat: Add endpoints for donors page --- listenbrainz/db/donation.py | 36 ++++++++++++- listenbrainz/db/playlist.py | 11 ++++ .../listenstore/timescale_listenstore.py | 23 ++++++++ listenbrainz/webserver/__init__.py | 3 ++ listenbrainz/webserver/views/donor_api.py | 4 +- listenbrainz/webserver/views/donors.py | 52 +++++++++++++++++++ 6 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 listenbrainz/webserver/views/donors.py diff --git a/listenbrainz/db/donation.py b/listenbrainz/db/donation.py index 0b5afef6ff..09b3f797d6 100644 --- a/listenbrainz/db/donation.py +++ b/listenbrainz/db/donation.py @@ -106,7 +106,18 @@ def get_recent_donors(meb_conn, db_conn, count: int, offset: int): }) donors = results.all() - return get_flairs_for_donors(db_conn, donors) + total_count_query = """ + SELECT COUNT(*) + FROM payment + WHERE editor_id IS NOT NULL + AND is_donation = 't' + AND payment_date >= (NOW() - INTERVAL '1 year') + """ + + result = meb_conn.execute(text(total_count_query)) + total_count = result.scalar() + + return get_flairs_for_donors(db_conn, donors), total_count def get_biggest_donors(meb_conn, db_conn, count: int, offset: int): @@ -154,7 +165,28 @@ def get_biggest_donors(meb_conn, db_conn, count: int, offset: int): }) donors = results.all() - return get_flairs_for_donors(db_conn, donors) + total_count_query = """ + WITH select_donations AS ( + SELECT editor_id + , currency + FROM payment + WHERE editor_id IS NOT NULL + AND is_donation = 't' + AND payment_date >= (NOW() - INTERVAL '1 year') + ) + SELECT COUNT(*) + FROM ( + SELECT editor_id + , currency + FROM select_donations + GROUP BY editor_id, currency + ) AS total_count; + """ + + result = meb_conn.execute(text(total_count_query)) + total_count = result.scalar() + + return get_flairs_for_donors(db_conn, donors), total_count def is_user_eligible_donor(meb_conn, musicbrainz_row_id: int): diff --git a/listenbrainz/db/playlist.py b/listenbrainz/db/playlist.py index 36564a9908..9b3fee9032 100644 --- a/listenbrainz/db/playlist.py +++ b/listenbrainz/db/playlist.py @@ -983,3 +983,14 @@ def get_playlist_recordings_metadata(mb_curs, ts_curs, playlist: Playlist) -> Pl rec.additional_metadata = additional_metadata return playlist + + +def get_playlist_count(ts_conn, creator_ids: List[str]): + query = text(""" + SELECT creator_id, COUNT(*) as count + FROM playlist.playlist + WHERE creator_id IN :creator_ids + GROUP BY creator_id + """) + result = ts_conn.execute(query, {"creator_ids": tuple(creator_ids)}) + return result.fetchall() diff --git a/listenbrainz/listenstore/timescale_listenstore.py b/listenbrainz/listenstore/timescale_listenstore.py index ac9caa2603..959a863ed1 100644 --- a/listenbrainz/listenstore/timescale_listenstore.py +++ b/listenbrainz/listenstore/timescale_listenstore.py @@ -86,6 +86,29 @@ def get_listen_count_for_user(self, user_id: int): cache.set(REDIS_USER_LISTEN_COUNT + str(user_id), count, REDIS_USER_LISTEN_COUNT_EXPIRY) return count + def get_listen_count_for_users(self, user_ids: list): + """Get the total number of listens for a list of users. + + Args: + user_ids: the list of users to get listens for + """ + cached_count_map = cache.get_many([REDIS_USER_LISTEN_COUNT + str(user_id) for user_id in user_ids]) + # Extract the user_ids for which we don't have cached counts. cached_cout is a dict of key-value pairs + # where key is the cache key and value is the cached value. We need to extract the user_id from the cache key. + listen_count = {int(key.split(".")[1]): value for key, value in cached_count_map.items()} + missing_user_ids = set(user_ids) - set(listen_count.keys()) + + if not missing_user_ids: + return listen_count + + query = "SELECT user_id, count, created FROM listen_user_metadata WHERE user_id = ANY(:user_ids)" + result = ts_conn.execute(sqlalchemy.text(query), {"user_ids": missing_user_ids}) + listen_count.update({row.user_id: row.count for row in result}) + cache.set_many({REDIS_USER_LISTEN_COUNT + str(row.user_id): row.count for row in result}, + expirein=REDIS_USER_LISTEN_COUNT_EXPIRY) + return listen_count + + def get_timestamps_for_user(self, user_id: int) -> Tuple[Optional[datetime], Optional[datetime]]: """ Return the min_ts and max_ts for the given list of users """ query = """ diff --git a/listenbrainz/webserver/__init__.py b/listenbrainz/webserver/__init__.py index 5e2063fd21..9bff0fa7be 100644 --- a/listenbrainz/webserver/__init__.py +++ b/listenbrainz/webserver/__init__.py @@ -355,6 +355,9 @@ def _register_blueprints(app): from listenbrainz.webserver.views.explore import explore_bp app.register_blueprint(explore_bp, url_prefix='/explore') + from listenbrainz.webserver.views.donors import donors_bp + app.register_blueprint(donors_bp, url_prefix='/donors') + from listenbrainz.webserver.views.api import api_bp app.register_blueprint(api_bp, url_prefix=API_PREFIX) diff --git a/listenbrainz/webserver/views/donor_api.py b/listenbrainz/webserver/views/donor_api.py index 5fb16be2c4..840fff5cd8 100644 --- a/listenbrainz/webserver/views/donor_api.py +++ b/listenbrainz/webserver/views/donor_api.py @@ -22,7 +22,7 @@ def recent_donors(): count = _parse_int_arg("count", DEFAULT_DONOR_COUNT) offset = _parse_int_arg("offset", 0) - donors = get_recent_donors(meb_conn, db_conn, count, offset) + donors, _ = get_recent_donors(meb_conn, db_conn, count, offset) return jsonify(donors) @@ -36,5 +36,5 @@ def biggest_donors(): count = _parse_int_arg("count", DEFAULT_DONOR_COUNT) offset = _parse_int_arg("offset", 0) - donors = get_biggest_donors(meb_conn, db_conn, count, offset) + donors, _ = get_biggest_donors(meb_conn, db_conn, count, offset) return jsonify(donors) diff --git a/listenbrainz/webserver/views/donors.py b/listenbrainz/webserver/views/donors.py new file mode 100644 index 0000000000..147131142c --- /dev/null +++ b/listenbrainz/webserver/views/donors.py @@ -0,0 +1,52 @@ +from flask import Blueprint, render_template, jsonify, request +from math import ceil + +from listenbrainz.webserver.views.api_tools import _parse_int_arg + +from listenbrainz.db.donation import get_recent_donors, get_biggest_donors +from listenbrainz.webserver import db_conn, meb_conn, ts_conn, timescale_connection +import listenbrainz.db.user as db_user +import listenbrainz.db.playlist as db_playlist + +DEFAULT_DONOR_COUNT = 25 +donors_bp = Blueprint("donors", __name__) + +@donors_bp.route("/", defaults={'path': ''}) +@donors_bp.route('//') +def donors(path): + return render_template("index.html") + + +@donors_bp.route("/", methods=["POST"]) +def donors_post(): + page = _parse_int_arg("page", 1) + sort = request.args.get("sort", "date") + offset = (int(page) - 1) * DEFAULT_DONOR_COUNT + + if sort == "amount": + donors, donation_count = get_biggest_donors(meb_conn, db_conn, DEFAULT_DONOR_COUNT, offset) + else: + donors, donation_count = get_recent_donors(meb_conn, db_conn, DEFAULT_DONOR_COUNT, offset) + + donation_count_pages = ceil(donation_count / DEFAULT_DONOR_COUNT) + + musicbrainz_ids = [donor["musicbrainz_id"] for donor in donors if donor['is_listenbrainz_user']] + donors_info = db_user.get_many_users_by_mb_id(db_conn, musicbrainz_ids) if musicbrainz_ids else {} + donor_ids = [donor_info.id for _, donor_info in donors_info.items()] + + user_listen_count = timescale_connection._ts.get_listen_count_for_users(donor_ids) if donor_ids else {} + user_playlist_count = db_playlist.get_playlist_count(ts_conn, donor_ids) if donor_ids else {} + + for donor in donors: + donor_info = donors_info.get(donor["musicbrainz_id"]) + if not donor_info: + donor['listenCount'] = None + donor['playlistCount'] = None + else: + donor['listenCount'] = user_listen_count.get(donor_info.id, 0) + donor['playlistCount'] = user_playlist_count.get(donor_info.id, 0) + + return jsonify({ + "data": donors, + "totalPageCount": donation_count_pages, + }) From 2e1eb3bcc6f671e0fac24ad1d24ca160e11d101c Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 16 Sep 2024 08:11:49 +0000 Subject: [PATCH 03/10] fix: return playlist count as dictionary --- listenbrainz/db/playlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/listenbrainz/db/playlist.py b/listenbrainz/db/playlist.py index 9b3fee9032..7d6c87a3cd 100644 --- a/listenbrainz/db/playlist.py +++ b/listenbrainz/db/playlist.py @@ -985,7 +985,7 @@ def get_playlist_recordings_metadata(mb_curs, ts_curs, playlist: Playlist) -> Pl return playlist -def get_playlist_count(ts_conn, creator_ids: List[str]): +def get_playlist_count(ts_conn, creator_ids: List[str]) -> dict: query = text(""" SELECT creator_id, COUNT(*) as count FROM playlist.playlist @@ -993,4 +993,4 @@ def get_playlist_count(ts_conn, creator_ids: List[str]): GROUP BY creator_id """) result = ts_conn.execute(query, {"creator_ids": tuple(creator_ids)}) - return result.fetchall() + return {row[0]: row[1] for row in result.fetchall()} From 5f6962c3b920436314ba5de746377064c52b6bb2 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 16 Sep 2024 09:25:41 +0000 Subject: [PATCH 04/10] fix: listen count is not being fetched properly --- frontend/js/src/donors/Donors.tsx | 4 ++-- listenbrainz/listenstore/timescale_listenstore.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 03a4883615..ed322540d9 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -121,7 +121,7 @@ function Donors() { {donor.currency === "usd" ? "$" : "€"} {donor.donation}

- {donor.musicbrainz_id && donor.is_listenbrainz_user && ( + {donor.musicbrainz_id && donor.is_listenbrainz_user ? (
{donor.listenCount && ( )}
- )} + ) : null}
))} diff --git a/listenbrainz/listenstore/timescale_listenstore.py b/listenbrainz/listenstore/timescale_listenstore.py index 959a863ed1..bd7140e4d8 100644 --- a/listenbrainz/listenstore/timescale_listenstore.py +++ b/listenbrainz/listenstore/timescale_listenstore.py @@ -95,16 +95,18 @@ def get_listen_count_for_users(self, user_ids: list): cached_count_map = cache.get_many([REDIS_USER_LISTEN_COUNT + str(user_id) for user_id in user_ids]) # Extract the user_ids for which we don't have cached counts. cached_cout is a dict of key-value pairs # where key is the cache key and value is the cached value. We need to extract the user_id from the cache key. - listen_count = {int(key.split(".")[1]): value for key, value in cached_count_map.items()} + listen_count = {int(key.split(".")[1]): value for key, value in cached_count_map.items() + if value is not None} missing_user_ids = set(user_ids) - set(listen_count.keys()) if not missing_user_ids: return listen_count query = "SELECT user_id, count, created FROM listen_user_metadata WHERE user_id = ANY(:user_ids)" - result = ts_conn.execute(sqlalchemy.text(query), {"user_ids": missing_user_ids}) - listen_count.update({row.user_id: row.count for row in result}) - cache.set_many({REDIS_USER_LISTEN_COUNT + str(row.user_id): row.count for row in result}, + result = ts_conn.execute(sqlalchemy.text(query), {"user_ids": list(missing_user_ids)}) + data = result.fetchall() + listen_count.update({row.user_id: row.count for row in data}) + cache.set_many({REDIS_USER_LISTEN_COUNT + str(row.user_id): row.count for row in data}, expirein=REDIS_USER_LISTEN_COUNT_EXPIRY) return listen_count From ef311fc5a84ec171856526948eadf17ae5a1f0aa Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 16 Sep 2024 09:37:54 +0000 Subject: [PATCH 05/10] fix: Hide Listen Count and Playlist Count if the value is not defined --- frontend/css/donors-page.less | 2 +- frontend/js/src/donors/Donors.tsx | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less index 654cd3554b..ff9493f8f0 100644 --- a/frontend/css/donors-page.less +++ b/frontend/css/donors-page.less @@ -19,7 +19,7 @@ margin-bottom: 0; } - .donation-amount { + .donation-user { font-weight: 600; font-size: 1.75rem; margin-bottom: 0.5rem; diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index ed322540d9..d9dcdb5466 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -17,7 +17,6 @@ import Loader from "../components/Loader"; type DonorLoaderData = { data: { id: number; - name: string; donated_at: string; donation: number; currency: "usd" | "eur"; @@ -93,20 +92,18 @@ function Donors() { {donors?.map((donor) => (
-

- {donor.musicbrainz_id}{" "} - {donor.musicbrainz_id ? ( - <> - ( +

+ {donor.musicbrainz_id && + (donor.is_listenbrainz_user ? ( {donor.musicbrainz_id} - ) - - ) : null} + ) : ( + {donor.musicbrainz_id} + ))}

@@ -123,7 +120,7 @@ function Donors() {

{donor.musicbrainz_id && donor.is_listenbrainz_user ? (
- {donor.listenCount && ( + {donor.listenCount ? ( {formatListenCount(donor.listenCount)} Listens - )} - {donor.playlistCount && ( + ) : null} + {donor.playlistCount ? ( {formatListenCount(donor.playlistCount)} Playlists - )} + ) : null}
) : null}
From 1f969e3cafdef0185c4af472c092095b88d40911 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Mon, 16 Sep 2024 10:01:41 +0000 Subject: [PATCH 06/10] fix: PEP8 --- listenbrainz/listenstore/timescale_listenstore.py | 2 +- listenbrainz/webserver/views/donors.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/listenbrainz/listenstore/timescale_listenstore.py b/listenbrainz/listenstore/timescale_listenstore.py index bd7140e4d8..648bf7c97b 100644 --- a/listenbrainz/listenstore/timescale_listenstore.py +++ b/listenbrainz/listenstore/timescale_listenstore.py @@ -106,7 +106,7 @@ def get_listen_count_for_users(self, user_ids: list): result = ts_conn.execute(sqlalchemy.text(query), {"user_ids": list(missing_user_ids)}) data = result.fetchall() listen_count.update({row.user_id: row.count for row in data}) - cache.set_many({REDIS_USER_LISTEN_COUNT + str(row.user_id): row.count for row in data}, + cache.set_many({REDIS_USER_LISTEN_COUNT + str(row.user_id): row.count for row in data}, expirein=REDIS_USER_LISTEN_COUNT_EXPIRY) return listen_count diff --git a/listenbrainz/webserver/views/donors.py b/listenbrainz/webserver/views/donors.py index 147131142c..6b1a7c3dab 100644 --- a/listenbrainz/webserver/views/donors.py +++ b/listenbrainz/webserver/views/donors.py @@ -11,6 +11,7 @@ DEFAULT_DONOR_COUNT = 25 donors_bp = Blueprint("donors", __name__) + @donors_bp.route("/", defaults={'path': ''}) @donors_bp.route('//') def donors(path): From 7be967a329080d0b9e353b0141627fc4b16d4123 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Tue, 17 Sep 2024 15:08:06 +0000 Subject: [PATCH 07/10] style: Improve Donor Card Design --- frontend/css/donors-page.less | 11 +++++++- frontend/js/src/donors/Donors.tsx | 42 ++++++++++++++++--------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less index ff9493f8f0..395479c383 100644 --- a/frontend/css/donors-page.less +++ b/frontend/css/donors-page.less @@ -3,7 +3,7 @@ flex-direction: column; justify-content: space-between; border: 1px solid #e2e8f0; - padding: 0.5rem 1.5rem; + padding: 1rem; margin: 1.5rem 0; @media (min-width: 640px) { flex-direction: row; @@ -36,6 +36,7 @@ display: flex; color: #6b7280; gap: 10px; + align-items: center; } } @@ -72,4 +73,12 @@ } } } + + &:hover { + background-color: @table-bg-hover; + } +} + +#donors { + margin-top: 1.5em; } diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index d9dcdb5466..665f58cbdd 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -71,28 +71,30 @@ function Donors() { return (
-

Donations

-
- handleSortBy("date")} - > - Date - - handleSortBy("amount")} - > - Amount - +
+

Donations

+
+ handleSortBy("date")} + > + Date + + handleSortBy("amount")} + > + Amount + +
{donors?.map((donor) => (
-

+

{donor.musicbrainz_id && (donor.is_listenbrainz_user ? ( {donor.musicbrainz_id} ))} -

-

+

+

Donation Date:{" "} {new Date(donor.donated_at).toLocaleDateString()}

-

+

From 4ff5c9296c96615cad26448c71eb9acc4a3624ce Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Tue, 17 Sep 2024 16:07:14 +0000 Subject: [PATCH 08/10] feat: Move Donor page link to bottom of sidebar --- frontend/js/src/components/Navbar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index ce05b86dd4..b072fb241d 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -97,9 +97,6 @@ function Navbar() { Explore - - Donors -

@@ -119,6 +116,9 @@ function Navbar() { About + + Donors + Date: Tue, 17 Sep 2024 16:15:52 +0000 Subject: [PATCH 09/10] style: Fix Donation date styling --- frontend/css/donors-page.less | 2 +- frontend/js/src/donors/Donors.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less index 395479c383..b32138bc26 100644 --- a/frontend/css/donors-page.less +++ b/frontend/css/donors-page.less @@ -36,7 +36,7 @@ display: flex; color: #6b7280; gap: 10px; - align-items: center; + align-items: baseline; } } diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 665f58cbdd..726aa7ba3f 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -109,10 +109,10 @@ function Donors() {
-

+ Donation Date:{" "} {new Date(donor.donated_at).toLocaleDateString()} -

+
From af8e24b8ab7105778bd0775c25b3fed398b5bb53 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Wed, 18 Sep 2024 03:44:27 +0000 Subject: [PATCH 10/10] feat: redirect to musicbrainz profile if user not a lb user --- frontend/js/src/donors/Donors.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 726aa7ba3f..efc08855c9 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -98,13 +98,22 @@ function Donors() { {donor.musicbrainz_id && (donor.is_listenbrainz_user ? ( {donor.musicbrainz_id} ) : ( - {donor.musicbrainz_id} + + {donor.musicbrainz_id} + ))}