From 426ed946e7295177d3674d964d6749a7e463cbf8 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Sun, 15 Sep 2024 22:53:28 +0000 Subject: [PATCH 01/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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} + ))}
From bf11b6bfcfd3ba69802a7016ed53b5acc7db0daa Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Tue, 24 Sep 2024 13:57:59 +0530 Subject: [PATCH 11/34] Remove unreachable condition --- frontend/js/src/donors/Donors.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index efc08855c9..3c895ba58a 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -98,9 +98,7 @@ function Donors() { {donor.musicbrainz_id && (donor.is_listenbrainz_user ? ( {donor.musicbrainz_id} From 04cdac61bb74aeb5851ec3fb61dd3bd3b46c5caf Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Mon, 9 Sep 2024 20:19:24 +0200 Subject: [PATCH 12/34] Set up Donations page --- frontend/css/donations.less | 30 +++++ frontend/css/homepage.less | 17 +-- frontend/css/main.less | 1 + frontend/js/src/about/donations/Donate.tsx | 147 +++++++++++++++++++++ frontend/js/src/about/layout.tsx | 1 + frontend/js/src/about/routes/index.tsx | 7 + 6 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 frontend/css/donations.less create mode 100644 frontend/js/src/about/donations/Donate.tsx diff --git a/frontend/css/donations.less b/frontend/css/donations.less new file mode 100644 index 0000000000..7f4299adf2 --- /dev/null +++ b/frontend/css/donations.less @@ -0,0 +1,30 @@ +#donations-page { + background: @homepage-background; +} + +#donations-tiers { + position: relative; + z-index: 1; + display: flex; + gap: 1em; + align-items: center; + .tier { + padding: 1em; + flex: 1; + height: 68vh; + scale:1; + &:hover{ + height:70vh; + scale: 1.1; + } + background: fadeout(white,20%); + transition: scale 0.4s ease-out; + } + .tier-heading { + text-align: center; + min-height: 150px; + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid @gray; + } +} \ No newline at end of file diff --git a/frontend/css/homepage.less b/frontend/css/homepage.less index 8f471c123d..fc74b9ab6e 100644 --- a/frontend/css/homepage.less +++ b/frontend/css/homepage.less @@ -1,5 +1,10 @@ @dark-grey: #46433a; @even-darker-grey: #353070; +@homepage-background: linear-gradient( + 288deg, + @dark-grey 16.96%, + @even-darker-grey 98.91% + ); #homepage-container { overflow-y: auto; height: 100vh; // absolute fallback @@ -16,11 +21,7 @@ } .homepage-upper { - background: linear-gradient( - 288deg, - @dark-grey 16.96%, - @even-darker-grey 98.91% - ); + background: @homepage-background; height: 100%; position: relative; padding-left: 50px; @@ -118,11 +119,7 @@ } .homepage-lower { - background: linear-gradient( - 288deg, - @dark-grey 16.96%, - @even-darker-grey 98.91% - ); + background: @homepage-background; height: 100%; position: relative; padding-left: 50px; diff --git a/frontend/css/main.less b/frontend/css/main.less index 55952c38a8..8fc1f5bae3 100644 --- a/frontend/css/main.less +++ b/frontend/css/main.less @@ -39,6 +39,7 @@ @import "search.less"; @import "accordion.less"; @import "donors-page.less"; +@import "donations.less"; @icon-font-path: "/static/fonts/"; diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx new file mode 100644 index 0000000000..72d44a1eda --- /dev/null +++ b/frontend/js/src/about/donations/Donate.tsx @@ -0,0 +1,147 @@ +import { faCheck } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import * as React from "react"; +import { COLOR_LB_GREEN } from "../../utils/constants"; + +export default function Data() { + return ( +
+
+

Money can’t buy happiness, but it can buy LISTENBRAINZ HOSTING

+

+ ListenBrainz is open-source and non-profit, and you can help us + survive and thrive with repeating or one-time donations. +

+
+
+
+

Free

+
+
+ {" "} + All website features, for free, forever +
+
+
+
+

10$ donation

+
+ +
+
+ {" "} + All website features, for free, forever +
+ +
+ {" "} + User flair +
+ + Your username will appear with a special effect on the website + for everyone to see + +
+
+ {" "} + Our eternal gratitude +
+
+ +   + Inner sense of peace and accomplishment +
+
+
+
+

50$ donation

+
+ +
+
+ {" "} + All website features, for free, forever +
+ +
+ {" "} + User flair +
+ + Your username will appear with a special effect on the website + for everyone to see + +
+
+ {" "} + Our eternal gratitude +
+
+ +   + Inner sense of peace and accomplishment +
+
+ +   + Make your momma proud +
+ + Or maybe show her you haven't changed, depending. + +
+
+ +   + Bragging rights +
+ + When we have taken over the world, you'll be able to + proudly say + + “I helped our music recommendation overlords get to + where they are today” + + +
+
+
+
+

Jokes aside

+

+ We believe in creating a free service for all where you do not pay + with your personal information and where you do not + become the product like so many other online services. +
+ Similarly, we want all features to ba available to everyone, and steer + away from a pay-to-play, "ListenBrainz Pro++" model. +

+

+ Of course, hosting and developing such a service costs money in the + real world, and we are not exempt from that reality. All the + information (listening history, popularity, etc.) is available + publicly and for free to anyone, but commercial entities are expected to support us + financially. That is not quite enough to pay for everything or to have + the resources we need to create the features you request. +

+

+ Please consider joining thousands of passionate contributors with a + one-time or regular donation. You will be helping this ecosystem + grow into an honest archipelago of music lovers. +

+
+
+
+ ); +} diff --git a/frontend/js/src/about/layout.tsx b/frontend/js/src/about/layout.tsx index 5a6d6b92f3..95b8ee4170 100644 --- a/frontend/js/src/about/layout.tsx +++ b/frontend/js/src/about/layout.tsx @@ -10,6 +10,7 @@ type Section = { const sections: Section[] = [ { to: "about/", label: "About" }, + { to: "donate/", label: "Donate" }, { to: "current-status/", label: "Site status" }, { to: "add-data/", label: "Submitting data" }, { to: "data/", label: "Using our data" }, diff --git a/frontend/js/src/about/routes/index.tsx b/frontend/js/src/about/routes/index.tsx index 4d9c0921c1..10531944e4 100644 --- a/frontend/js/src/about/routes/index.tsx +++ b/frontend/js/src/about/routes/index.tsx @@ -52,6 +52,13 @@ const getAboutRoutes = (): RouteObject[] => { }, ], }, + { + path: "donate/", + lazy: async () => { + const Donate = await import("../donations/Donate"); + return { Component: Donate.default }; + }, + }, ]; return routes; }; From 1ccd9be5fb4de6ff6ba9d4cca67bf7a6d1f82833 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 11 Sep 2024 18:14:26 +0200 Subject: [PATCH 13/34] Improve donations page design and text --- frontend/css/donations.less | 62 +++++++- frontend/js/src/about/donations/Donate.tsx | 158 ++++++++++++++------- 2 files changed, 159 insertions(+), 61 deletions(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index 7f4299adf2..3c134c8cce 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -1,24 +1,71 @@ #donations-page { background: @homepage-background; + margin-left: -15px; + margin-right: -15px; + margin-bottom: -20px; + padding: 3em 2em; + position: relative; + .donations-page-header { + text-align: center; + margin-bottom: 2em; + } + .donations-page-header,.donations-page-header > h1{ + color: @white + } + .donations-page-footer { + z-index: 1; + position: relative; + } + .grey-wedge { + height: 530px; + bottom: 0; + clip-path: polygon(100% 100%, 100% 18%,0px 0%, 0% 100%); + background: #e9e9e9; + position: absolute; + left: 0; + width: 100%; + } + .blob { + position: absolute; + top: 25%; + opacity: 70%; + } } #donations-tiers { position: relative; + width: 90%; + margin-left: auto; + margin-right: auto; + margin-bottom: 3em; z-index: 1; display: flex; - gap: 1em; - align-items: center; + flex-wrap: wrap; + gap: 1.5em; + align-items: stretch; + // height:70vh; .tier { padding: 1em; flex: 1; - height: 68vh; + flex-basis: 250px; scale:1; + height: auto; &:hover{ - height:70vh; scale: 1.1; } - background: fadeout(white,20%); - transition: scale 0.4s ease-out; + background: fadeout(white,30%); + transition: scale 0.2s ease-in-out; + // blurred glass effect + // background: rgba(255, 255, 255, 0.2); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.3); + &:first-child { + flex-basis: 100%; + height: auto; + } } .tier-heading { text-align: center; @@ -27,4 +74,7 @@ padding-bottom: 1em; border-bottom: 1px solid @gray; } + .perk { + margin-bottom: 0.5em; + } } \ No newline at end of file diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 72d44a1eda..f5e0c9859f 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -2,31 +2,88 @@ import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as React from "react"; import { COLOR_LB_GREEN } from "../../utils/constants"; +import Blob from "../../home/Blob"; export default function Data() { return (
-
-

Money can’t buy happiness, but it can buy LISTENBRAINZ HOSTING

-

- ListenBrainz is open-source and non-profit, and you can help us - survive and thrive with repeating or one-time donations. -

+ + +
+
+

+ Money can't buy happiness, but it can buy +
+ LISTENBRAINZ HOSTING +

+

+ ListenBrainz is a free, open-source and non-profit project. +
+ If you enjoy it, you can help us survive and thrive with your + donations. +
+ At our scale, every contribution matters. +

+
+
+
+

+ Free for everyone +

+
+
+ {" "} + All website features, for free, forever +
+
-

Free

+

+ 5$ +
+ donation +

+
+
{" "} All website features, for free, forever
+ +
+ {" "} + User flair +
+ + Add a special effect to your username on the website + +
+
+ {" "} + Our eternal gratitude +
-

10$ donation

+

+ 20$ +
+ donation +


- +
{" "} @@ -38,8 +95,7 @@ export default function Data() { User flair
- Your username will appear with a special effect on the website - for everyone to see + Add a special effect to your username on the website
@@ -54,9 +110,15 @@ export default function Data() {
-

50$ donation

+

+ 50$ +
+ donation +


- +
{" "} @@ -68,8 +130,7 @@ export default function Data() { User flair
- Your username will appear with a special effect on the website - for everyone to see + Add a special effect to your username on the website
@@ -86,9 +147,7 @@ export default function Data() {   Make your momma proud
- - Or maybe show her you haven't changed, depending. - + Or maybe show her you haven't changed
@@ -96,8 +155,7 @@ export default function Data() { Bragging rights
- When we have taken over the world, you'll be able to - proudly say + When we have taken over the world, you can proudly say “I helped our music recommendation overlords get to where they are today” @@ -106,42 +164,32 @@ export default function Data() {
-
-

Jokes aside

-

- We believe in creating a free service for all where you do not pay - with your personal information and where you do not - become the product like so many other online services. -
- Similarly, we want all features to ba available to everyone, and steer - away from a pay-to-play, "ListenBrainz Pro++" model. -

-

- Of course, hosting and developing such a service costs money in the - real world, and we are not exempt from that reality. All the - information (listening history, popularity, etc.) is available - publicly and for free to anyone, but commercial entities are expected to support us - financially. That is not quite enough to pay for everything or to have - the resources we need to create the features you request. -

-

- Please consider joining thousands of passionate contributors with a - one-time or regular donation. You will be helping this ecosystem - grow into an honest archipelago of music lovers. -

+
+

Jokes aside

+

+ Join our music network, where you aren't the product and + your personal data isn't the price you pay. +
+ We believe everyone should have access to all features —no paywalls, + no “Pro++” features. +
+ All features are free for everyone. +
+
+ While it takes real-world money to keep us going, all our data is + open-srouce and free for everyone. +
+ Commercial users are expected to contribute back and support us, but + it's not enough to fund the new features you want. +
+
+ By donating —either once or regularly— you'll join thousands of + music lovers in helping us build an honest, unbiased and + community-driven space for music discovery. +

+
-
+
); } From 68ad54f1426a73ab903b2346fd301e53fe399e26 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 11 Sep 2024 18:35:17 +0200 Subject: [PATCH 14/34] Donate page: buttons link to MetaBrainz donate page as a temporary measure, better than doing nothing if this goes live before we have donations implemented in LB --- frontend/js/src/about/donations/Donate.tsx | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index f5e0c9859f..54d7855e74 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -51,7 +51,13 @@ export default function Data() { donation
-
@@ -81,7 +87,13 @@ export default function Data() { donation
-
@@ -116,7 +128,13 @@ export default function Data() { donation
-
From 57f624605cf421e2b7f28c3ffe41ea9a925d12f4 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 11 Sep 2024 18:35:58 +0200 Subject: [PATCH 15/34] Donate: Add link to nav menu --- frontend/js/src/components/Navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index b072fb241d..f55ce884bb 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -117,7 +117,7 @@ function Navbar() { About - Donors + Donations
Date: Wed, 11 Sep 2024 18:40:49 +0200 Subject: [PATCH 16/34] Lint in my bellybutton --- frontend/css/donations.less | 147 ++++++++++++++++++------------------ frontend/css/homepage.less | 8 +- 2 files changed, 78 insertions(+), 77 deletions(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index 3c134c8cce..59e298d124 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -1,80 +1,81 @@ #donations-page { - background: @homepage-background; - margin-left: -15px; - margin-right: -15px; - margin-bottom: -20px; - padding: 3em 2em; + background: @homepage-background; + margin-left: -15px; + margin-right: -15px; + margin-bottom: -20px; + padding: 3em 2em; + position: relative; + .donations-page-header { + text-align: center; + margin-bottom: 2em; + } + .donations-page-header, + .donations-page-header > h1 { + color: @white; + } + .donations-page-footer { + z-index: 1; position: relative; - .donations-page-header { - text-align: center; - margin-bottom: 2em; - } - .donations-page-header,.donations-page-header > h1{ - color: @white - } - .donations-page-footer { - z-index: 1; - position: relative; - } - .grey-wedge { - height: 530px; - bottom: 0; - clip-path: polygon(100% 100%, 100% 18%,0px 0%, 0% 100%); - background: #e9e9e9; - position: absolute; - left: 0; - width: 100%; - } - .blob { - position: absolute; - top: 25%; - opacity: 70%; - } + } + .grey-wedge { + height: 530px; + bottom: 0; + clip-path: polygon(100% 100%, 100% 18%, 0px 0%, 0% 100%); + background: #e9e9e9; + position: absolute; + left: 0; + width: 100%; + } + .blob { + position: absolute; + top: 25%; + opacity: 70%; + } } #donations-tiers { - position: relative; - width: 90%; - margin-left: auto; - margin-right: auto; - margin-bottom: 3em; - z-index: 1; - display: flex; - flex-wrap: wrap; - gap: 1.5em; - align-items: stretch; - // height:70vh; - .tier { - padding: 1em; - flex: 1; - flex-basis: 250px; - scale:1; - height: auto; - &:hover{ - scale: 1.1; - } - background: fadeout(white,30%); - transition: scale 0.2s ease-in-out; - // blurred glass effect - // background: rgba(255, 255, 255, 0.2); - border-radius: 16px; - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); - border: 1px solid rgba(255, 255, 255, 0.3); - &:first-child { - flex-basis: 100%; - height: auto; - } + position: relative; + width: 90%; + margin-left: auto; + margin-right: auto; + margin-bottom: 3em; + z-index: 1; + display: flex; + flex-wrap: wrap; + gap: 1.5em; + align-items: stretch; + // height:70vh; + .tier { + padding: 1em; + flex: 1; + flex-basis: 250px; + scale: 1; + height: auto; + &:hover { + scale: 1.1; } - .tier-heading { - text-align: center; - min-height: 150px; - margin-bottom: 1em; - padding-bottom: 1em; - border-bottom: 1px solid @gray; + background: fadeout(white, 30%); + transition: scale 0.2s ease-in-out; + // blurred glass effect + // background: rgba(255, 255, 255, 0.2); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.3); + &:first-child { + flex-basis: 100%; + height: auto; } - .perk { - margin-bottom: 0.5em; - } -} \ No newline at end of file + } + .tier-heading { + text-align: center; + min-height: 150px; + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid @gray; + } + .perk { + margin-bottom: 0.5em; + } +} diff --git a/frontend/css/homepage.less b/frontend/css/homepage.less index fc74b9ab6e..021d32cf8a 100644 --- a/frontend/css/homepage.less +++ b/frontend/css/homepage.less @@ -1,10 +1,10 @@ @dark-grey: #46433a; @even-darker-grey: #353070; @homepage-background: linear-gradient( - 288deg, - @dark-grey 16.96%, - @even-darker-grey 98.91% - ); + 288deg, + @dark-grey 16.96%, + @even-darker-grey 98.91% +); #homepage-container { overflow-y: auto; height: 100vh; // absolute fallback From a4c7ce2cceef669617aef3da668de4537235fd6f Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 11 Sep 2024 19:00:53 +0200 Subject: [PATCH 17/34] Update frontend/js/src/about/donations/Donate.tsx Co-authored-by: David Kellner <52860029+kellnerd@users.noreply.github.com> --- frontend/js/src/about/donations/Donate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 54d7855e74..3795f7f451 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -195,7 +195,7 @@ export default function Data() {

While it takes real-world money to keep us going, all our data is - open-srouce and free for everyone. + open-source and free for everyone.
Commercial users are expected to contribute back and support us, but it's not enough to fund the new features you want. From 6e20a96b49a98ea886603b00acde6e453d1bdb73 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 11 Sep 2024 19:44:24 +0200 Subject: [PATCH 18/34] Donatiosn page: use lists with custom icons as suggested by kellnerd --- frontend/css/donations.less | 4 +- frontend/js/src/about/donations/Donate.tsx | 220 +++++++++++++-------- 2 files changed, 141 insertions(+), 83 deletions(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index 59e298d124..b93205a2fc 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -44,7 +44,9 @@ flex-wrap: wrap; gap: 1.5em; align-items: stretch; - // height:70vh; + ul { + margin-left: 1.5em; + } .tier { padding: 1em; flex: 1; diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 3795f7f451..4d426dd0e3 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -38,9 +38,12 @@ export default function Data() { Free for everyone
-
- {" "} - All website features, for free, forever +
+ + + All website features, for free, forever + +
@@ -61,23 +64,36 @@ export default function Data() { Donate
-
- {" "} - All website features, for free, forever -
- -
- {" "} - User flair -
- - Add a special effect to your username on the website - -
-
- {" "} - Our eternal gratitude -
+
    +
  • + + All website features, for free, forever +
  • +
  • + + User flair +
    + + Add a special effect to your username on the website + +
  • +
  • + + Our eternal gratitude +
  • +
@@ -97,28 +113,45 @@ export default function Data() { Donate
-
- {" "} - All website features, for free, forever -
+
    +
  • + + All website features, for free, forever +
  • -
    - {" "} - User flair -
    - - Add a special effect to your username on the website - -
    -
    - {" "} - Our eternal gratitude -
    -
    - -   - Inner sense of peace and accomplishment -
    +
  • + + User flair +
    + + Add a special effect to your username on the website + +
  • +
  • + + Our eternal gratitude +
  • +
  • + + Inner sense of peace and accomplishment +
  • +
@@ -138,48 +171,71 @@ export default function Data() { Donate
-
- {" "} - All website features, for free, forever -
+
    +
  • + + All website features, for free, forever +
  • -
    - {" "} - User flair -
    - - Add a special effect to your username on the website - -
    -
    - {" "} - Our eternal gratitude -
    -
    - -   - Inner sense of peace and accomplishment -
    -
    - -   - Make your momma proud -
    - Or maybe show her you haven't changed -
    -
    - -   - Bragging rights -
    - - When we have taken over the world, you can proudly say - - “I helped our music recommendation overlords get to - where they are today” - - -
    +
  • + + User flair +
    + + Add a special effect to your username on the website + +
  • +
  • + + Our eternal gratitude +
  • +
  • + + Inner sense of peace and accomplishment +
  • +
  • + + Make your momma proud +
    + Or maybe show her you haven't changed +
  • +
  • + + Bragging rights +
    + + When we have taken over the world, you can proudly say + + “I helped our music recommendation overlords get to + where they are today” + + +
  • +
From dae9f83e5bb8094d0bffc9d6b664ab386d318e00 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Wed, 25 Sep 2024 14:06:09 +0530 Subject: [PATCH 19/34] Pair down text on donations page --- frontend/js/src/about/donations/Donate.tsx | 85 +++++----------------- 1 file changed, 19 insertions(+), 66 deletions(-) diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 4d426dd0e3..1a901d4ebd 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -17,11 +17,11 @@ export default function Data() { />
-

+

Money can't buy happiness, but it can buy
LISTENBRAINZ HOSTING -

+

ListenBrainz is a free, open-source and non-profit project.
@@ -49,30 +49,18 @@ export default function Data() {

- 5$ -
- donation -

-
+
    -
  • - - All website features, for free, forever -
  • - 20$ -
    - donation -

    -
    +
      -
    • - - All website features, for free, forever -
    • -
    • - 50$ -
      - donation -

      -
      +
        -
      • - - All website features, for free, forever -
      • -
      • - Make your momma proud -
        - Or maybe show her you haven't changed + Make your family proud
      • - Bragging rights -
        - - When we have taken over the world, you can proudly say - - “I helped our music recommendation overlords get to - where they are today” - - + Instant street cred +
      • +
      • + + De-shittify the internet
@@ -250,13 +210,6 @@ export default function Data() { All features are free for everyone.

- While it takes real-world money to keep us going, all our data is - open-source and free for everyone. -
- Commercial users are expected to contribute back and support us, but - it's not enough to fund the new features you want. -
-
By donating —either once or regularly— you'll join thousands of music lovers in helping us build an honest, unbiased and community-driven space for music discovery. From 8dabb663ae4a6ae5c4cc796d3ed19921188877de Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 26 Sep 2024 18:00:19 +0530 Subject: [PATCH 20/34] More flexible blobs That's all the description you'll get --- frontend/js/src/home/Blob.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/js/src/home/Blob.tsx b/frontend/js/src/home/Blob.tsx index 47d4cb8abb..ba5cce3a6b 100644 --- a/frontend/js/src/home/Blob.tsx +++ b/frontend/js/src/home/Blob.tsx @@ -1,10 +1,13 @@ import * as React from "react"; import * as blobs2Animate from "blobs/v2/animate"; +import { isUndefined } from "lodash"; type BlobProps = { width: number; height: number; randomness: number; + seed?: number; + blur?: number; style?: React.CSSProperties; } & Pick; @@ -13,6 +16,8 @@ export default function Blob({ height, className, randomness, + seed, + blur, style, }: BlobProps) { const blobCanvas = React.useRef(null); @@ -24,7 +29,9 @@ export default function Blob({ } const animation = blobs2Animate.canvasPath(); const randomAngleStart = Math.random() * 360; - + if (!isUndefined(blur) && Number.isFinite(blur)) { + ctx.filter = `blur(${blur}px)`; + } const renderAnimation: FrameRequestCallback = (time) => { ctx.clearRect(0, 0, width, height); let angle = (((time / 50) % 360) / 180) * Math.PI; @@ -43,20 +50,26 @@ export default function Blob({ }; requestAnimationFrame(renderAnimation); - const size = Math.min(width, height); - + let size = Math.min(width, height); + let offsetX = 0; + let offsetY = 0; + if (!isUndefined(blur) && Number.isFinite(blur)) { + size -= blur * 2; + offsetX = blur; + offsetY = blur; + } blobs2Animate.wigglePreset( animation, { - seed: Date.now(), + seed: seed ?? Date.now(), extraPoints: 3, randomness: randomness * 2, size, }, - { offsetX: 0, offsetY: 0 }, + { offsetX, offsetY }, { speed: Math.random() * 1.7 } ); - }, [blobCanvas, height, width, randomness]); + }, [blobCanvas, height, width, randomness, blur, seed]); return ( Date: Thu, 26 Sep 2024 18:01:05 +0530 Subject: [PATCH 21/34] Add banner on donors page inviting to go to the donations page --- frontend/css/donors-page.less | 29 ++++++++++++++++++- frontend/js/src/donors/Donors.tsx | 46 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less index b32138bc26..16a730a536 100644 --- a/frontend/css/donors-page.less +++ b/frontend/css/donors-page.less @@ -80,5 +80,32 @@ } #donors { - margin-top: 1.5em; + margin-top: 15px; + .donations-page-header { + .donation-header-text { + position: relative; + color: @white; + font-size: 16px; + } + a { + color: @black; + font-weight: bold; + margin-left: 2em; + &.btn{ + border-radius: @border-radius-base; + } + } + opacity: 0.85; + border-radius: @border-radius-large; + background: @homepage-background; + padding: 3em 2em; + position: relative; + text-align: center; + margin-bottom: 2em; + overflow: hidden; + canvas { + position: absolute; + opacity: 0.5; + } + } } diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 3c895ba58a..90ab301611 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -13,6 +13,7 @@ import Pagination from "../common/Pagination"; import { getObjectForURLSearchParams } from "../utils/utils"; import { RouteQuery } from "../utils/Loader"; import Loader from "../components/Loader"; +import Blob from "../home/Blob"; type DonorLoaderData = { data: { @@ -71,6 +72,51 @@ function Donors() { return (
+
+ + + + +
+ Money can't buy happiness, but it can buy  + LISTENBRAINZ HOSTING + + Donate + +
+

Donations

From 095cccbbbe291b421c012e87d708cdb00484e832 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 26 Sep 2024 18:02:37 +0530 Subject: [PATCH 22/34] Pair down donations page text --- frontend/css/donations.less | 4 +- frontend/js/src/about/donations/Donate.tsx | 74 ++++++++++------------ 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index b93205a2fc..5f86509ca8 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -9,9 +9,9 @@ text-align: center; margin-bottom: 2em; } - .donations-page-header, - .donations-page-header > h1 { + .donations-page-header{ color: @white; + font-size: 24px; } .donations-page-footer { z-index: 1; diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 1a901d4ebd..8fdec2e224 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { COLOR_LB_GREEN } from "../../utils/constants"; import Blob from "../../home/Blob"; -export default function Data() { +export default function Donate() { return (
@@ -17,19 +17,9 @@ export default function Data() { />
-

- Money can't buy happiness, but it can buy -
- LISTENBRAINZ HOSTING -

-

- ListenBrainz is a free, open-source and non-profit project. -
- If you enjoy it, you can help us survive and thrive with your - donations. -
- At our scale, every contribution matters. -

+ Money can't buy happiness, but it can buy +
+ LISTENBRAINZ HOSTING
@@ -49,15 +39,15 @@ export default function Data() {

- + Donate $5 +

    @@ -86,16 +76,16 @@ export default function Data() {

    - -

    + +
    • @@ -131,15 +121,15 @@ export default function Data() {

      - +

        @@ -213,6 +203,8 @@ export default function Data() { By donating —either once or regularly— you'll join thousands of music lovers in helping us build an honest, unbiased and community-driven space for music discovery. +
        + At our scale, every contribution matters.

    From aa4d614373235537a56049e48f3c2bb2604be994 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Wed, 9 Oct 2024 06:25:32 +0000 Subject: [PATCH 23/34] feat: Add Recent Donors Card --- frontend/css/donations.less | 2 - frontend/css/donors-page.less | 27 ++++++- frontend/js/src/donors/Donors.tsx | 11 +-- frontend/js/src/recent/RecentListens.tsx | 7 +- .../js/src/recent/components/RecentDonors.tsx | 79 +++++++++++++++++++ frontend/js/src/utils/donation.d.ts | 14 ++++ .../js/tests/recent/RecentListens.test.tsx | 1 + 7 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 frontend/js/src/recent/components/RecentDonors.tsx create mode 100644 frontend/js/src/utils/donation.d.ts diff --git a/frontend/css/donations.less b/frontend/css/donations.less index 5f86509ca8..f71205db78 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -8,8 +8,6 @@ .donations-page-header { text-align: center; margin-bottom: 2em; - } - .donations-page-header{ color: @white; font-size: 24px; } diff --git a/frontend/css/donors-page.less b/frontend/css/donors-page.less index 16a730a536..000f848b41 100644 --- a/frontend/css/donors-page.less +++ b/frontend/css/donors-page.less @@ -91,7 +91,7 @@ color: @black; font-weight: bold; margin-left: 2em; - &.btn{ + &.btn { border-radius: @border-radius-base; } } @@ -109,3 +109,28 @@ } } } + +.recent-donor-card { + gap: 1rem; + > div { + width: 150px; + } + .user-link { + word-wrap: break-word; + } + .donor-pinned-recording { + display: flex; + align-items: center; + background: #edf2f7; + gap: 0.5rem; + margin-left: auto; + border-radius: 10px; + .text { + color: #6b7280; + text-wrap: wrap; + } + svg { + color: #353070; + } + } +} diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 90ab301611..10f5dc69b4 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -16,16 +16,7 @@ import Loader from "../components/Loader"; import Blob from "../home/Blob"; type DonorLoaderData = { - data: { - id: number; - donated_at: string; - donation: number; - currency: "usd" | "eur"; - musicbrainz_id: string; - is_listenbrainz_user: boolean; - listenCount: number; - playlistCount: number; - }[]; + data: DonationInfo[]; totalPageCount: number; }; diff --git a/frontend/js/src/recent/RecentListens.tsx b/frontend/js/src/recent/RecentListens.tsx index e44294ec00..03d55ba804 100644 --- a/frontend/js/src/recent/RecentListens.tsx +++ b/frontend/js/src/recent/RecentListens.tsx @@ -9,11 +9,13 @@ import Card from "../components/Card"; import { getTrackName } from "../utils/utils"; import { useBrainzPlayerDispatch } from "../common/brainzplayer/BrainzPlayerContext"; +import RecentDonorsCard from "./components/RecentDonors"; export type RecentListensProps = { listens: Array; globalListenCount: number; globalUserCount: string; + recentDonors: Array; }; type RecentListensLoaderData = RecentListensProps; @@ -36,7 +38,7 @@ export default class RecentListens extends React.Component< render() { const { listens } = this.state; - const { globalListenCount, globalUserCount } = this.props; + const { globalListenCount, globalUserCount, recentDonors } = this.props; return (
    @@ -62,6 +64,9 @@ export default class RecentListens extends React.Component< users
    + + +
{!listens.length && ( diff --git a/frontend/js/src/recent/components/RecentDonors.tsx b/frontend/js/src/recent/components/RecentDonors.tsx new file mode 100644 index 0000000000..582dff876b --- /dev/null +++ b/frontend/js/src/recent/components/RecentDonors.tsx @@ -0,0 +1,79 @@ +import { faThumbtack } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import * as React from "react"; +import { Link } from "react-router-dom"; +import { + getRecordingMBID, + getTrackName, + pinnedRecordingToListen, +} from "../../utils/utils"; + +type RecentDonorsCardProps = { + donors: DonationInfoWithPinnedRecording[]; +}; + +function RecentDonorsCard(props: RecentDonorsCardProps) { + const { donors } = props; + + return ( + <> +

+ Recent Donors +

+
+ {donors && + donors.map((donor) => { + const pinnedRecordingListen = donor.pinnedRecording + ? pinnedRecordingToListen(donor.pinnedRecording) + : null; + return ( +
+
+ {donor.musicbrainz_id && + (donor.is_listenbrainz_user ? ( + + {donor.musicbrainz_id} + + ) : ( + + {donor.musicbrainz_id} + + ))} +

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

+
+ {pinnedRecordingListen && ( + + +
+ {getTrackName(pinnedRecordingListen)} +
+ + )} +
+ ); + })} +
+ + ); +} + +export default RecentDonorsCard; diff --git a/frontend/js/src/utils/donation.d.ts b/frontend/js/src/utils/donation.d.ts new file mode 100644 index 0000000000..6ee3f068fd --- /dev/null +++ b/frontend/js/src/utils/donation.d.ts @@ -0,0 +1,14 @@ +type DonationInfo = { + id: number; + donated_at: string; + donation: number; + currency: "usd" | "eur"; + musicbrainz_id: string; + is_listenbrainz_user: boolean; + listenCount: number; + playlistCount: number; +}; + +type DonationInfoWithPinnedRecording = DonationInfo & { + pinnedRecording: PinnedRecording; +}; diff --git a/frontend/js/tests/recent/RecentListens.test.tsx b/frontend/js/tests/recent/RecentListens.test.tsx index 43ffc7171f..833d5c5fc4 100644 --- a/frontend/js/tests/recent/RecentListens.test.tsx +++ b/frontend/js/tests/recent/RecentListens.test.tsx @@ -57,6 +57,7 @@ const props = { userPinnedRecording, globalListenCount, globalUserCount, + recentDonors: [], }; // Create a new instance of GlobalAppContext From 50a23f475706e2b0eeeacb124571878cea86554d Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Wed, 9 Oct 2024 06:29:23 +0000 Subject: [PATCH 24/34] feat: Return Pinned Recording and Donor Info in props --- listenbrainz/db/pinned_recording.py | 29 ++++++++++++++++------- listenbrainz/webserver/views/index.py | 34 +++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/listenbrainz/db/pinned_recording.py b/listenbrainz/db/pinned_recording.py index b5f1e327c6..ea3863ef0d 100644 --- a/listenbrainz/db/pinned_recording.py +++ b/listenbrainz/db/pinned_recording.py @@ -103,25 +103,38 @@ def delete(db_conn, row_id: int, user_id: int): return result.rowcount == 1 -def get_current_pin_for_user(db_conn, user_id: int) -> PinnedRecording: - """ Get the currently active pinned recording for the user if they have one. +def get_current_pin_for_users(db_conn, user_ids: Iterable[int]) -> List[PinnedRecording]: + """ Get the currently active pinned recording for the users if they have one. Args: db_conn: database connection - user_id: the row ID of the user in the DB + user_ids: the row IDs of the users in the DB Returns: - A PinnedRecording object. + A list of PinnedRecording objects. """ result = db_conn.execute(sqlalchemy.text(""" SELECT {columns} FROM pinned_recording as pin - WHERE (user_id = :user_id + WHERE (user_id IN :user_ids AND pinned_until >= NOW()) - """.format(columns=','.join(PINNED_REC_GET_COLUMNS))), {'user_id': user_id}) - row = result.mappings().first() - return PinnedRecording(**row) if row else None + """.format(columns=','.join(PINNED_REC_GET_COLUMNS))), {"user_ids": tuple(user_ids)}) + return [PinnedRecording(**row) if row else None for row in result.mappings()] + + +def get_current_pin_for_user(db_conn, user_id: int) -> PinnedRecording: + """ Get the currently active pinned recording for the user if they have one. + + Args: + db_conn: database connection + user_id: the row ID of the user in the DB + + Returns: + A PinnedRecording object. + """ + + return next(iter(get_current_pin_for_users(db_conn, [user_id])), None) def get_pin_history_for_user(db_conn, user_id: int, count: int, offset: int) -> List[PinnedRecording]: diff --git a/listenbrainz/webserver/views/index.py b/listenbrainz/webserver/views/index.py index 22168a3d7e..aa3705e811 100644 --- a/listenbrainz/webserver/views/index.py +++ b/listenbrainz/webserver/views/index.py @@ -18,11 +18,14 @@ from listenbrainz.background.background_tasks import add_task from listenbrainz.db.exceptions import DatabaseException from listenbrainz.webserver.decorators import web_listenstore_needed -from listenbrainz.webserver import flash, db_conn +from listenbrainz.webserver import flash, db_conn, meb_conn, ts_conn from listenbrainz.webserver.timescale_connection import _ts from listenbrainz.webserver.redis_connection import _redis import listenbrainz.db.stats as db_stats import listenbrainz.db.user_relationship as db_user_relationship +from listenbrainz.db.donation import get_recent_donors +from listenbrainz.db.pinned_recording import get_current_pin_for_users +from listenbrainz.db.msid_mbid_mapping import fetch_track_metadata_for_items from listenbrainz.webserver.views.user_timeline_event_api import get_feed_events_for_user index_bp = Blueprint('index', __name__) @@ -136,10 +139,37 @@ def recent_listens(): except DatabaseException as e: user_count = 'Unknown' + recent_donors, _ = get_recent_donors(meb_conn, db_conn, 25, 0) + + # Get MusicBrainz IDs for donors who are ListenBrainz users + musicbrainz_ids = [donor["musicbrainz_id"] + for donor in recent_donors + if donor.get('is_listenbrainz_user')] + + # Fetch donor info only if there are valid MusicBrainz IDs + 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.values()] + + # Get current pinned recordings + pinned_recordings_data = {} + if donor_ids: + pinned_recordings = get_current_pin_for_users(db_conn, donor_ids) + if pinned_recordings: + pinned_recordings_metadata = fetch_track_metadata_for_items(ts_conn, pinned_recordings) + # Map recordings by user_id for quick lookup + pinned_recordings_data = {recording.user_id: dict(recording) + for recording in pinned_recordings_metadata} + + # Add pinned recordings to recent donors + for donor in recent_donors: + donor_info = donors_info.get(donor["musicbrainz_id"]) + donor["pinnedRecording"] = pinned_recordings_data.get(donor_info.id) if donor_info else None + props = { "listens": recent, "globalListenCount": listen_count, - "globalUserCount": user_count + "globalUserCount": user_count, + "recentDonors": recent_donors, } return jsonify(props) From 13cbf264bc1677dbd12dc95957882a87ba7b4191 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Wed, 9 Oct 2024 06:52:43 +0000 Subject: [PATCH 25/34] feat: Add Margins to Card --- frontend/js/src/recent/RecentListens.tsx | 4 ++-- frontend/js/src/recent/components/RecentDonors.tsx | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/js/src/recent/RecentListens.tsx b/frontend/js/src/recent/RecentListens.tsx index 03d55ba804..437b62e183 100644 --- a/frontend/js/src/recent/RecentListens.tsx +++ b/frontend/js/src/recent/RecentListens.tsx @@ -57,14 +57,14 @@ export default class RecentListens extends React.Component< songs played
- +
{globalUserCount ?? "-"}
users
- +
diff --git a/frontend/js/src/recent/components/RecentDonors.tsx b/frontend/js/src/recent/components/RecentDonors.tsx index 582dff876b..497ec86c64 100644 --- a/frontend/js/src/recent/components/RecentDonors.tsx +++ b/frontend/js/src/recent/components/RecentDonors.tsx @@ -15,6 +15,10 @@ type RecentDonorsCardProps = { function RecentDonorsCard(props: RecentDonorsCardProps) { const { donors } = props; + if (!donors || donors.length === 0) { + return null; + } + return ( <>

From 909aa54670fe77613302d8278250e010039a958e Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 11 Oct 2024 13:57:33 +0000 Subject: [PATCH 26/34] feat: Improve margins and add link to donations page --- frontend/js/src/recent/RecentListens.tsx | 2 +- frontend/js/src/recent/components/RecentDonors.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/js/src/recent/RecentListens.tsx b/frontend/js/src/recent/RecentListens.tsx index 437b62e183..30e6f83a24 100644 --- a/frontend/js/src/recent/RecentListens.tsx +++ b/frontend/js/src/recent/RecentListens.tsx @@ -64,7 +64,7 @@ export default class RecentListens extends React.Component< users

- +
diff --git a/frontend/js/src/recent/components/RecentDonors.tsx b/frontend/js/src/recent/components/RecentDonors.tsx index 497ec86c64..68eedd7c45 100644 --- a/frontend/js/src/recent/components/RecentDonors.tsx +++ b/frontend/js/src/recent/components/RecentDonors.tsx @@ -23,6 +23,10 @@ function RecentDonorsCard(props: RecentDonorsCardProps) { <>

Recent Donors +
+ + See all donations +

{donors && From ec9a1fe5330c1db3716ce78fc6fe191576494a54 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Mon, 14 Oct 2024 15:32:52 +0200 Subject: [PATCH 27/34] Donation tiers: tweak headers and add prefilled amount to donate url The ?amount=... param was added to the MeB.org donation page in https://github.com/metabrainz/metabrainz.org/pull/483 --- frontend/css/donations.less | 5 +---- frontend/js/src/about/donations/Donate.tsx | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index f71205db78..a793a48762 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -70,10 +70,7 @@ } .tier-heading { text-align: center; - min-height: 150px; - margin-bottom: 1em; - padding-bottom: 1em; - border-bottom: 1px solid @gray; + margin-bottom: 1.5em; } .perk { margin-bottom: 0.5em; diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 8fdec2e224..f72e3b439e 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -41,9 +41,12 @@ export default function Donate() {

+

    @@ -76,18 +72,14 @@ export default function Donate() {
      @@ -124,18 +116,14 @@ export default function Donate() {

      - +

        From 3cf6b6ed0cb2f980db1d581d1103efffb5e45662 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 24 Oct 2024 17:55:45 +0200 Subject: [PATCH 30/34] Donation tiers: Rework explanation text, add links --- frontend/js/src/about/donations/Donate.tsx | 54 ++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 3bfae6f7ea..2a02b7a5db 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -1,6 +1,7 @@ import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as React from "react"; +import { Link } from "react-router-dom"; import { COLOR_LB_GREEN } from "../../utils/constants"; import Blob from "../../home/Blob"; @@ -182,24 +183,41 @@ export default function Donate() {
    -
    -

    Jokes aside

    -

    - Join our music network, where you aren't the product and - your personal data isn't the price you pay. -
    - We believe everyone should have access to all features —no paywalls, - no “Pro++” features. -
    - All features are free for everyone. -
    -
    - By donating —either once or regularly— you'll join thousands of - music lovers in helping us build an honest, unbiased and - community-driven space for music discovery. -
    - At our scale, every contribution matters. -

    +
    +

    Jokes aside

    +
    +

    + We are a{" "} + + non-profit foundation + {" "} + and we are free to build the music website of our dreams, unbiased + by financial deals. +
    + One where you aren't the product and your personal + data isn't the price you pay. +
    + All features are free for everyone. —no paywalls, no + “Pro++” features. +

    +
    +
    +

    + By donating —either once or regularly— you'll join thousands + of music lovers in helping us build an honest, unbiased and + community-driven space for music discovery. +

    +

    + At our scale, every contribution matters. +

    +

    + See all our donors +

    +
    From ed570a6b9ea410ea67db916cef90db49eb72674d Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 24 Oct 2024 17:57:29 +0200 Subject: [PATCH 31/34] Donors page: reuse top banner --- frontend/js/src/donors/Donors.tsx | 94 ++++++++++++++++--------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 10f5dc69b4..163d0b5c65 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -60,54 +60,57 @@ function Donors() { sort: sortBy, }); }; + const donateBanner = ( +
    + + + + +
    + Money can't buy happiness, but it can buy  + LISTENBRAINZ HOSTING + + Donate + +
    +
    + ); return (
    -
    - - - - -
    - Money can't buy happiness, but it can buy  - LISTENBRAINZ HOSTING - - Donate - -
    -
    + {donateBanner}

    Donations

    @@ -196,6 +199,7 @@ function Donors() { handleClickPrevious={handleClickPrevious} handleClickNext={handleClickNext} /> + {donateBanner}
    ); } From c22aac639b41c9f3e4a2eb239f230da338890f73 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 24 Oct 2024 18:11:28 +0200 Subject: [PATCH 32/34] Update donation links --- frontend/js/src/about/About.tsx | 4 ++-- frontend/js/src/components/Footer.tsx | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/js/src/about/About.tsx b/frontend/js/src/about/About.tsx index 20bbe9d290..e3bb45309f 100644 --- a/frontend/js/src/about/About.tsx +++ b/frontend/js/src/about/About.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { Link } from "react-router-dom"; export default function About() { return ( @@ -87,8 +88,7 @@ export default function About() {

    Listenbrainz is a free open source project that is not run for profit. If you would like to help the project out financially, consider{" "} - donating to the MetaBrainz - Foundation. + donating to the MetaBrainz Foundation.

    Developers

    diff --git a/frontend/js/src/components/Footer.tsx b/frontend/js/src/components/Footer.tsx index 0fb91c08a0..2b2ca26b91 100644 --- a/frontend/js/src/components/Footer.tsx +++ b/frontend/js/src/components/Footer.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { faAnglesRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "react-router-dom"; export default function Footer() { return ( @@ -50,13 +51,7 @@ export default function Footer() {

    • {" "} - - Donate - + Donate
    • {" "} From a0da25417f6b6c8b3ade761f344b6feb5006273a Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Thu, 24 Oct 2024 18:35:28 +0200 Subject: [PATCH 33/34] Fix punctuation --- frontend/js/src/about/About.tsx | 2 +- frontend/js/src/about/donations/Donate.tsx | 6 +++--- frontend/js/src/components/Footer.tsx | 2 +- frontend/js/src/donors/Donors.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/js/src/about/About.tsx b/frontend/js/src/about/About.tsx index e3bb45309f..f68602fc14 100644 --- a/frontend/js/src/about/About.tsx +++ b/frontend/js/src/about/About.tsx @@ -88,7 +88,7 @@ export default function About() {

      Listenbrainz is a free open source project that is not run for profit. If you would like to help the project out financially, consider{" "} - donating to the MetaBrainz Foundation. + donating to the MetaBrainz Foundation.

      Developers

      diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index 2a02b7a5db..f9271517c0 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -201,8 +201,8 @@ export default function Donate() { One where you aren't the product and your personal data isn't the price you pay.
      - All features are free for everyone. —no paywalls, no - “Pro++” features. + All features are free for everyone —no paywalls, no “Pro++” + features.

    @@ -215,7 +215,7 @@ export default function Donate() { At our scale, every contribution matters.

    - See all our donors + See all our donors

    diff --git a/frontend/js/src/components/Footer.tsx b/frontend/js/src/components/Footer.tsx index 2b2ca26b91..bc62bcc607 100644 --- a/frontend/js/src/components/Footer.tsx +++ b/frontend/js/src/components/Footer.tsx @@ -51,7 +51,7 @@ export default function Footer() {
    • {" "} - Donate + Donate
    • {" "} diff --git a/frontend/js/src/donors/Donors.tsx b/frontend/js/src/donors/Donors.tsx index 163d0b5c65..a52c5f79e0 100644 --- a/frontend/js/src/donors/Donors.tsx +++ b/frontend/js/src/donors/Donors.tsx @@ -101,7 +101,7 @@ function Donors() {
      Money can't buy happiness, but it can buy  LISTENBRAINZ HOSTING - + Donate
      From e52b7a47b1b8c58a81b699a7050b3a2bdda771e9 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Fri, 25 Oct 2024 12:41:13 +0200 Subject: [PATCH 34/34] Donations: increase explanation font size --- frontend/css/donations.less | 1 + frontend/js/src/about/donations/Donate.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/css/donations.less b/frontend/css/donations.less index a793a48762..fe0c93ece5 100644 --- a/frontend/css/donations.less +++ b/frontend/css/donations.less @@ -14,6 +14,7 @@ .donations-page-footer { z-index: 1; position: relative; + font-size: 1.5rem; } .grey-wedge { height: 530px; diff --git a/frontend/js/src/about/donations/Donate.tsx b/frontend/js/src/about/donations/Donate.tsx index f9271517c0..45a12561d0 100644 --- a/frontend/js/src/about/donations/Donate.tsx +++ b/frontend/js/src/about/donations/Donate.tsx @@ -200,7 +200,8 @@ export default function Donate() {
      One where you aren't the product and your personal data isn't the price you pay. -
      +

      +

      All features are free for everyone —no paywalls, no “Pro++” features.