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 { format, formatDistanceToNow } from "date-fns";
|
||||||
import { useAuth } from "../hooks/use-auth";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Listen } from "../api/entities/listen";
|
|
||||||
import { getRecentListens } from "../api/api";
|
|
||||||
import { Redirect } from "react-router-dom";
|
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;
|
const LISTENS_PER_PAGE = 15;
|
||||||
|
|
||||||
|
|
@ -15,66 +17,120 @@ export const RecentListens: React.FC = () => {
|
||||||
const [listens, setListens] = useState<Listen[]>([]);
|
const [listens, setListens] = useState<Listen[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadListensForPage = async () => {
|
||||||
(async () => {
|
setIsLoading(true);
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const listensFromApi = await getRecentListens({
|
const listensFromApi = await getRecentListens({
|
||||||
page,
|
page,
|
||||||
limit: LISTENS_PER_PAGE,
|
limit: LISTENS_PER_PAGE,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (totalPages !== listensFromApi.meta.totalPages) {
|
if (totalPages !== listensFromApi.meta.totalPages) {
|
||||||
setTotalPages(listensFromApi.meta.totalPages);
|
setTotalPages(listensFromApi.meta.totalPages);
|
||||||
}
|
|
||||||
setListens(listensFromApi.items);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error while fetching recent listens:", err);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
})();
|
setListens(listensFromApi.items);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error while fetching recent listens:", err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadListensForPage();
|
||||||
}, [user, page, totalPages]);
|
}, [user, page, totalPages]);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return <Redirect to="/" />;
|
return <Redirect to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
return (
|
||||||
return (
|
<div className="lg:flex lg:justify-center p-4">
|
||||||
<div>
|
<div className="lg:flex-shrink-0 min-w-full lg:min-w-0 lg:w-2/3 max-w-screen-lg">
|
||||||
<span>Loading Listens</span>
|
<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>
|
||||||
);
|
</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 (
|
return (
|
||||||
<div>
|
<div className="flex justify-center">
|
||||||
<p>Recent listens</p>
|
<button
|
||||||
<div>
|
className={`${
|
||||||
{listens.length === 0 && (
|
isFirstPage ? disabledBtn : "hover:bg-gray-400"
|
||||||
<div>
|
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-l`}
|
||||||
<p>Could not find any listens!</p>
|
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>
|
</div>
|
||||||
)}
|
)
|
||||||
{listens.map((listen) => (
|
)}
|
||||||
<ListenItem listen={listen} />
|
<button
|
||||||
))}
|
className={`${
|
||||||
</div>
|
isLastPage ? disabledBtn : "hover:bg-gray-400"
|
||||||
<div>
|
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-r`}
|
||||||
<p>Page: {page}</p>
|
onClick={() => setPage(page + 1)}
|
||||||
{page !== 1 && (
|
disabled={isLastPage}
|
||||||
<p>
|
>
|
||||||
<button onClick={() => setPage(page - 1)}>Previous Page</button>
|
Next
|
||||||
</p>
|
</button>
|
||||||
)}
|
|
||||||
{page !== totalPages && (
|
|
||||||
<p>
|
|
||||||
<button onClick={() => setPage(page + 1)}>Next Page</button>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -85,9 +141,17 @@ const ListenItem: React.FC<{ listen: Listen }> = ({ listen }) => {
|
||||||
const timeAgo = formatDistanceToNow(new Date(listen.playedAt), {
|
const timeAgo = formatDistanceToNow(new Date(listen.playedAt), {
|
||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
});
|
});
|
||||||
|
const dateTime = format(new Date(listen.playedAt), "PP p");
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="hover:bg-gray-100 border-b border-gray-200 lg:flex lg:justify-around text-gray-700 px-4 py-2">
|
||||||
{trackName} - {artists} - {timeAgo}
|
<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>
|
</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