mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
feat(frontend): style recent listens page
This commit is contained in:
parent
75d3e2edbd
commit
1d5cefb447
3 changed files with 165 additions and 54 deletions
|
|
@ -1,9 +1,11 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useAuth } from "../hooks/use-auth";
|
||||
import { Listen } from "../api/entities/listen";
|
||||
import { getRecentListens } from "../api/api";
|
||||
import { format, formatDistanceToNow } from "date-fns";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { getRecentListens } from "../api/api";
|
||||
import { Listen } from "../api/entities/listen";
|
||||
import { useAuth } from "../hooks/use-auth";
|
||||
import { ReloadIcon } from "../icons/Reload";
|
||||
import { getPaginationItems } from "../util/getPaginationItems";
|
||||
|
||||
const LISTENS_PER_PAGE = 15;
|
||||
|
||||
|
|
@ -15,66 +17,120 @@ export const RecentListens: React.FC = () => {
|
|||
const [listens, setListens] = useState<Listen[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
const loadListensForPage = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const listensFromApi = await getRecentListens({
|
||||
page,
|
||||
limit: LISTENS_PER_PAGE,
|
||||
});
|
||||
try {
|
||||
const listensFromApi = await getRecentListens({
|
||||
page,
|
||||
limit: LISTENS_PER_PAGE,
|
||||
});
|
||||
|
||||
if (totalPages !== listensFromApi.meta.totalPages) {
|
||||
setTotalPages(listensFromApi.meta.totalPages);
|
||||
}
|
||||
setListens(listensFromApi.items);
|
||||
} catch (err) {
|
||||
console.error("Error while fetching recent listens:", err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (totalPages !== listensFromApi.meta.totalPages) {
|
||||
setTotalPages(listensFromApi.meta.totalPages);
|
||||
}
|
||||
})();
|
||||
setListens(listensFromApi.items);
|
||||
} catch (err) {
|
||||
console.error("Error while fetching recent listens:", err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadListensForPage();
|
||||
}, [user, page, totalPages]);
|
||||
|
||||
if (!user) {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<span>Loading Listens</span>
|
||||
return (
|
||||
<div className="lg:flex lg:justify-center p-4">
|
||||
<div className="lg:flex-shrink-0 min-w-full lg:min-w-0 lg:w-2/3 max-w-screen-lg">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-2xl font-normal text-gray-700">Recent listens</p>
|
||||
<button
|
||||
className="flex-shrink-0 mx-2 bg-transparent hover:bg-green-500 text-green-500 hover:text-white font-semibold py-2 px-4 border border-green-500 hover:border-transparent rounded"
|
||||
onClick={loadListensForPage}
|
||||
>
|
||||
<ReloadIcon className="w-5 h-5 fill-current" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading && (
|
||||
<div>
|
||||
<span>Loading Listens</span>
|
||||
</div>
|
||||
)}
|
||||
{listens.length === 0 && (
|
||||
<div>
|
||||
<p>Could not find any listens!</p>
|
||||
</div>
|
||||
)}
|
||||
{listens.length > 0 && (
|
||||
<div className="table-auto my-2 w-full text-gray-700">
|
||||
{listens.map((listen) => (
|
||||
<ListenItem listen={listen} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Pagination page={page} totalPages={totalPages} setPage={setPage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Pagination: React.FC<{
|
||||
page: number;
|
||||
totalPages: number;
|
||||
setPage: (newPage: number) => void;
|
||||
}> = ({ page, totalPages, setPage }) => {
|
||||
const disabledBtn = "opacity-50 cursor-default";
|
||||
const pageButtons = getPaginationItems(page, totalPages, 1);
|
||||
|
||||
const isFirstPage = page === 1;
|
||||
const isLastPage = page === totalPages;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Recent listens</p>
|
||||
<div>
|
||||
{listens.length === 0 && (
|
||||
<div>
|
||||
<p>Could not find any listens!</p>
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
className={`${
|
||||
isFirstPage ? disabledBtn : "hover:bg-gray-400"
|
||||
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-l`}
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={isFirstPage}
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
{pageButtons.map((buttonPage) =>
|
||||
buttonPage ? (
|
||||
<button
|
||||
className={`${
|
||||
buttonPage === page
|
||||
? "bg-green-300 hover:bg-green-400"
|
||||
: "bg-gray-300 hover:bg-gray-400"
|
||||
} text-gray-700 font-bold py-2 px-4`}
|
||||
onClick={() => setPage(buttonPage)}
|
||||
>
|
||||
{buttonPage}
|
||||
</button>
|
||||
) : (
|
||||
<div className="opacity-50 cursor-default bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-l">
|
||||
...
|
||||
</div>
|
||||
)}
|
||||
{listens.map((listen) => (
|
||||
<ListenItem listen={listen} />
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<p>Page: {page}</p>
|
||||
{page !== 1 && (
|
||||
<p>
|
||||
<button onClick={() => setPage(page - 1)}>Previous Page</button>
|
||||
</p>
|
||||
)}
|
||||
{page !== totalPages && (
|
||||
<p>
|
||||
<button onClick={() => setPage(page + 1)}>Next Page</button>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
className={`${
|
||||
isLastPage ? disabledBtn : "hover:bg-gray-400"
|
||||
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-r`}
|
||||
onClick={() => setPage(page + 1)}
|
||||
disabled={isLastPage}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -85,9 +141,17 @@ const ListenItem: React.FC<{ listen: Listen }> = ({ listen }) => {
|
|||
const timeAgo = formatDistanceToNow(new Date(listen.playedAt), {
|
||||
addSuffix: true,
|
||||
});
|
||||
const dateTime = format(new Date(listen.playedAt), "PP p");
|
||||
return (
|
||||
<div>
|
||||
{trackName} - {artists} - {timeAgo}
|
||||
<div className="hover:bg-gray-100 border-b border-gray-200 lg:flex lg:justify-around text-gray-700 px-4 py-2">
|
||||
<div className="lg:w-1/3 font-bold">{trackName}</div>
|
||||
<div className=" lg:w-1/3">{artists}</div>
|
||||
<div
|
||||
className=" lg:w-1/6 text-gray-500 font-thin text-sm"
|
||||
title={dateTime}
|
||||
>
|
||||
{timeAgo}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
18
frontend/src/icons/Reload.tsx
Normal file
18
frontend/src/icons/Reload.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
|
||||
export const ReloadIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0"
|
||||
y="0"
|
||||
viewBox="0 0 65 65"
|
||||
fill="#fff"
|
||||
{...props}
|
||||
>
|
||||
<g fill="fill">
|
||||
<path d="M32.5 4.999a27.31 27.31 0 00-14.699 4.282l-5.75-5.75v16.11h16.11l-6.395-6.395a21.834 21.834 0 0110.734-2.82c12.171 0 22.073 9.902 22.073 22.074 0 2.899-.577 5.664-1.599 8.202l4.738 2.762A27.299 27.299 0 0060 32.5C60 17.336 47.663 4.999 32.5 4.999zM43.227 51.746c-3.179 1.786-6.826 2.827-10.726 2.827-12.171 0-22.073-9.902-22.073-22.073 0-2.739.524-5.35 1.439-7.771l-4.731-2.851A27.34 27.34 0 005 32.5C5 47.664 17.336 60 32.5 60c5.406 0 10.434-1.584 14.691-4.289l5.758 5.759V45.358H36.838l6.389 6.388z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
29
frontend/src/util/getPaginationItems.ts
Normal file
29
frontend/src/util/getPaginationItems.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export const getPaginationItems = (
|
||||
currentPage: number,
|
||||
totalPages: number,
|
||||
delta: number = 1
|
||||
): (number | null)[] => {
|
||||
const left = currentPage - delta;
|
||||
const right = currentPage + delta;
|
||||
|
||||
const range = Array.from(Array(totalPages))
|
||||
.map((e, i) => i + 1)
|
||||
.filter((i) => i === 1 || i === totalPages || (i >= left && i <= right))
|
||||
.reduce((pages: (number | null)[], page, i) => {
|
||||
if (pages.length !== 0) {
|
||||
const prevPage = pages[pages.length - 1];
|
||||
if (prevPage !== null) {
|
||||
const diff = page - prevPage;
|
||||
if (diff > 1) {
|
||||
pages.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
|
||||
return pages;
|
||||
}, []);
|
||||
|
||||
return range;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue