feat(frontend): Dark Mode

This commit is contained in:
Julian Tölle 2022-07-24 17:45:29 +02:00
parent 65b0d24903
commit 78c7afc152
16 changed files with 83 additions and 62 deletions

View file

@ -21,7 +21,7 @@ export function App() {
} }
return ( return (
<div className="flex flex-col h-screen justify-between"> <div className="flex flex-col min-h-screen justify-between dark:bg-gray-900">
<header> <header>
<NavBar /> <NavBar />
</header> </header>

View file

@ -7,7 +7,7 @@ const VERSION = process.env.REACT_APP_VERSION || "Unknown";
export const Footer: React.FC = () => { export const Footer: React.FC = () => {
return ( return (
<div className="flex items-center justify-between flex-wrap bg-green-500 p-4 text-green-200 hover:text-white text-xs"> <div className="flex items-center justify-between flex-wrap bg-green-500 dark:bg-gray-800 p-4 text-green-200 hover:text-white text-xs">
<div> <div>
<a <a
href={CHANGELOG_URL} href={CHANGELOG_URL}

View file

@ -9,13 +9,13 @@ export const LoginFailure: React.FC = () => {
return ( return (
<div <div
className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 m-8" className="bg-yellow-100 dark:bg-gray-800 border-l-4 border-yellow-500 text-yellow-700 dark:text-yellow-100 p-4 m-8"
role="alert" role="alert"
> >
<p className="font-bold">Login Failure</p> <p className="font-bold">Login Failure</p>
<p>Something not ideal might be happening.</p> <p>Something not ideal might be happening.</p>
<p className="m-5 bg-gray-100 p-5"> <p className="m-5 bg-gray-100 dark:bg-gray-700 p-5">
<ul className="text-xs text-gray-700 font-mono"> <ul className="text-xs text-gray-700 dark:text-gray-300 font-mono">
<li>Source: {source}</li> <li>Source: {source}</li>
<li>Reason: {reason}</li> <li>Reason: {reason}</li>
</ul> </ul>

View file

@ -2,12 +2,12 @@ import React from "react";
import { Spinner } from "./Spinner"; import { Spinner } from "./Spinner";
export const LoginLoading: React.FC = () => ( export const LoginLoading: React.FC = () => (
<main className="md:flex md:justify-center p-4"> <main className="sm:flex sm:justify-center p-4 dark:bg-gray-900 h-screen">
<div className="md:shrink-0 min-w-full md:min-w-0 md:w-1/3 l:w-1/5 xl:w-1/6 max-w-screen-lg shadow-xl bg-gray-100 rounded-lg m-2"> <div className="sm:shrink-0 min-w-full sm:min-w-0 sm:w-1/2 md:w-1/3 l:w-1/5 xl:w-1/5 2xl:w-1/6 max-w-screen-lg max-h-min h-min shadow-xl bg-gray-100 dark:bg-gray-800 rounded-lg m-2">
<div className="text-white bg-green-500 rounded-lg rounded-b-none text-center mb-4 p-6"> <div className="text-white bg-green-500 dark:bg-gray-700 rounded-lg rounded-b-none text-center mb-4 p-6">
<span className="font-semibold text-xl tracking-tight">Listory</span> <span className="font-semibold text-xl tracking-tight">Listory</span>
</div> </div>
<p className="text-2xl font-extralight text-gray-700 text-center p-6"> <p className="text-2xl font-extralight text-gray-700 dark:text-gray-300 text-center p-6">
Logging in Logging in
</p> </p>
<Spinner className="p-6" size={128} /> <Spinner className="p-6" size={128} />

View file

@ -8,7 +8,7 @@ export const NavBar: React.FC = () => {
const { user, loginWithSpotifyProps } = useAuth(); const { user, loginWithSpotifyProps } = useAuth();
return ( return (
<div className="flex items-center justify-between flex-wrap bg-green-500 p-6"> <div className="flex items-center justify-between flex-wrap bg-green-500 dark:bg-gray-800 p-6">
<div className="flex items-center shrink-0 text-white mr-6"> <div className="flex items-center shrink-0 text-white mr-6">
<span className="font-semibold text-xl tracking-tight">Listory</span> <span className="font-semibold text-xl tracking-tight">Listory</span>
</div> </div>

View file

@ -32,7 +32,9 @@ export const RecentListens: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Recent listens</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Recent listens
</p>
<button <button
className="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" className="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={reload} onClick={reload}
@ -40,16 +42,18 @@ export const RecentListens: React.FC = () => {
<ReloadIcon className="w-5 h-5 fill-current" /> <ReloadIcon className="w-5 h-5 fill-current" />
</button> </button>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-2 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-2 m-2">
{isLoading && <Spinner className="m-8" />} {isLoading && <Spinner className="m-8" />}
{recentListens.length === 0 && ( {recentListens.length === 0 && (
<div> <div className="text-center m-4">
<p>Could not find any listens!</p> <p className="text-gray-700 dark:text-gray-400">
Could not find any listens!
</p>
</div> </div>
)} )}
<div> <div>
{recentListens.length > 0 && ( {recentListens.length > 0 && (
<div className="table-auto w-full text-gray-700"> <div className="table-auto w-full">
{recentListens.map((listen) => ( {recentListens.map((listen) => (
<ListenItem listen={listen} key={listen.id} /> <ListenItem listen={listen} key={listen.id} />
))} ))}
@ -69,6 +73,9 @@ const Pagination: React.FC<{
setPage: (newPage: number) => void; setPage: (newPage: number) => void;
}> = ({ page, totalPages, setPage }) => { }> = ({ page, totalPages, setPage }) => {
const disabledBtn = "opacity-50 cursor-default"; const disabledBtn = "opacity-50 cursor-default";
const hoverBtn = "hover:bg-gray-400 dark:hover:bg-gray-600";
const defaultBtn =
"bg-gray-300 dark:bg-gray-700 text-gray-700 dark:text-gray-300 font-bold py-2 px-4";
const pageButtons = getPaginationItems(page, totalPages, 1); const pageButtons = getPaginationItems(page, totalPages, 1);
const isFirstPage = page === 1; const isFirstPage = page === 1;
@ -78,8 +85,8 @@ const Pagination: React.FC<{
<div className="flex justify-center my-4"> <div className="flex justify-center my-4">
<button <button
className={`${ className={`${
isFirstPage ? disabledBtn : "hover:bg-gray-400" isFirstPage ? disabledBtn : hoverBtn
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-l`} } ${defaultBtn} rounded-l`}
onClick={() => setPage(page - 1)} onClick={() => setPage(page - 1)}
disabled={isFirstPage} disabled={isFirstPage}
> >
@ -88,11 +95,10 @@ const Pagination: React.FC<{
{pageButtons.map((buttonPage, i) => {pageButtons.map((buttonPage, i) =>
buttonPage ? ( buttonPage ? (
<button <button
className={`${ className={`${hoverBtn} ${defaultBtn} ${
buttonPage === page buttonPage === page &&
? "bg-green-300 hover:bg-green-400" "bg-green-400 dark:bg-green-500 hover:bg-green-400 dark:hover:bg-green-500 cursor-default"
: "bg-gray-300 hover:bg-gray-400" }`}
} text-gray-700 font-bold py-2 px-4`}
onClick={() => setPage(buttonPage)} onClick={() => setPage(buttonPage)}
key={i} key={i}
> >
@ -101,7 +107,7 @@ const Pagination: React.FC<{
) : ( ) : (
<div <div
key={i} key={i}
className="cursor-default bg-gray-300/50 text-gray-700/50 font-bold py-2 px-4 rounded-l" className={`cursor-default ${disabledBtn} ${defaultBtn}`}
> >
... ...
</div> </div>
@ -109,8 +115,8 @@ const Pagination: React.FC<{
)} )}
<button <button
className={`${ className={`${
isLastPage ? disabledBtn : "hover:bg-gray-400" isLastPage ? disabledBtn : hoverBtn
} bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-r`} } ${defaultBtn} rounded-r`}
onClick={() => setPage(page + 1)} onClick={() => setPage(page + 1)}
disabled={isLastPage} disabled={isLastPage}
> >
@ -128,7 +134,7 @@ const ListenItem: React.FC<{ listen: Listen }> = ({ listen }) => {
}); });
const dateTime = format(new Date(listen.playedAt), "PP p"); const dateTime = format(new Date(listen.playedAt), "PP p");
return ( return (
<div className="hover:bg-gray-100 border-b border-gray-200 md:flex md:justify-around text-gray-700 px-2 py-2"> <div className="hover:bg-gray-100 dark:hover:bg-gray-700 border-b border-gray-200 dark:border-gray-700/25 md:flex md:justify-around text-gray-700 dark:text-gray-300 px-2 py-2">
<div className="md:w-1/2 font-bold">{trackName}</div> <div className="md:w-1/2 font-bold">{trackName}</div>
<div className=" md:w-1/3">{artists}</div> <div className=" md:w-1/3">{artists}</div>
<div <div

View file

@ -47,14 +47,16 @@ export const ReportListens: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Listen Report</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Listen Report
</p>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-5 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
<div className="md:flex"> <div className="md:flex">
<div className="text-gray-700"> <div className="text-gray-700 dark:text-gray-300 mr-2">
<label className="text-sm">Timeframe</label> <label className="text-sm">Timeframe</label>
<select <select
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring" className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
onChange={(e) => onChange={(e) =>
setTimeFrame( setTimeFrame(
e.target.value as "day" | "week" | "month" | "year" e.target.value as "day" | "week" | "month" | "year"
@ -79,7 +81,7 @@ export const ReportListens: React.FC = () => {
</div> </div>
)} )}
{reportHasItems && ( {reportHasItems && (
<div className="w-full text-gray-700 mt-5"> <div className="w-full text-gray-700 dark:text-gray-300 mt-5">
<ReportGraph timeFrame={timeFrame} data={report} /> <ReportGraph timeFrame={timeFrame} data={report} />
</div> </div>
)} )}
@ -111,7 +113,7 @@ const ReportGraph: React.FC<{
const date = format(label as number, dateFormatFromTimeFrame(timeFrame)); const date = format(label as number, dateFormatFromTimeFrame(timeFrame));
return ( return (
<div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light"> <div className="bg-gray-100 dark:bg-gray-700 shadow-xl p-2 rounded text-sm font-light">
<p>{date}</p> <p>{date}</p>
<p> <p>
Listens: <span className="font-bold">{listens}</span> Listens: <span className="font-bold">{listens}</span>

View file

@ -24,10 +24,10 @@ export const ReportTimeOptions: React.FC<ReportTimeOptionsProps> = ({
}) => { }) => {
return ( return (
<div className="md:flex mb-4"> <div className="md:flex mb-4">
<div className="text-gray-700"> <div className="text-gray-700 dark:text-gray-300">
<label className="text-sm">Timeframe</label> <label className="text-sm">Timeframe</label>
<select <select
className="block appearance-none min-w-full md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring" className="block appearance-none min-w-full md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
onChange={(e) => onChange={(e) =>
setTimeOptions({ setTimeOptions({
...timeOptions, ...timeOptions,
@ -44,21 +44,25 @@ export const ReportTimeOptions: React.FC<ReportTimeOptionsProps> = ({
</select> </select>
</div> </div>
{timeOptions.timePreset === TimePreset.CUSTOM && ( {timeOptions.timePreset === TimePreset.CUSTOM && (
<div className="md:flex text-gray-700"> <div className="md:flex text-gray-700 dark:text-gray-200">
<DateSelect <div className="pl-2">
label="Start" <DateSelect
value={timeOptions.customTimeStart} label="Start"
onChange={(newDate) => value={timeOptions.customTimeStart}
setTimeOptions({ ...timeOptions, customTimeStart: newDate }) onChange={(newDate) =>
} setTimeOptions({ ...timeOptions, customTimeStart: newDate })
/> }
<DateSelect />
label="End" </div>
value={timeOptions.customTimeEnd} <div className="pl-2">
onChange={(newDate) => <DateSelect
setTimeOptions({ ...timeOptions, customTimeEnd: newDate }) label="End"
} value={timeOptions.customTimeEnd}
/> onChange={(newDate) =>
setTimeOptions({ ...timeOptions, customTimeEnd: newDate })
}
/>
</div>
</div> </div>
)} )}
</div> </div>

View file

@ -36,9 +36,11 @@ export const ReportTopAlbums: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Top Albums</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Top Albums
</p>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-5 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
<ReportTimeOptions <ReportTimeOptions
timeOptions={timeOptions} timeOptions={timeOptions}
setTimeOptions={setTimeOptions} setTimeOptions={setTimeOptions}

View file

@ -35,9 +35,11 @@ export const ReportTopArtists: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Top Artists</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Top Artists
</p>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-5 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
<ReportTimeOptions <ReportTimeOptions
timeOptions={timeOptions} timeOptions={timeOptions}
setTimeOptions={setTimeOptions} setTimeOptions={setTimeOptions}

View file

@ -40,9 +40,11 @@ export const ReportTopGenres: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Top Genres</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Top Genres
</p>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-5 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
<ReportTimeOptions <ReportTimeOptions
timeOptions={timeOptions} timeOptions={timeOptions}
setTimeOptions={setTimeOptions} setTimeOptions={setTimeOptions}

View file

@ -37,9 +37,11 @@ export const ReportTopTracks: React.FC = () => {
<div className="md:flex md:justify-center p-4"> <div className="md:flex md:justify-center p-4">
<div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg"> <div className="md:shrink-0 min-w-full xl:min-w-0 xl:w-2/3 max-w-screen-lg">
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-2xl font-normal text-gray-700">Top Tracks</p> <p className="text-2xl font-normal text-gray-700 dark:text-gray-400">
Top Tracks
</p>
</div> </div>
<div className="shadow-xl bg-gray-100 rounded p-5 m-2"> <div className="shadow-xl bg-gray-100 dark:bg-gray-800 rounded p-5 m-2">
<ReportTimeOptions <ReportTimeOptions
timeOptions={timeOptions} timeOptions={timeOptions}
setTimeOptions={setTimeOptions} setTimeOptions={setTimeOptions}

View file

@ -12,7 +12,7 @@ export const Spinner: React.FC<SpinnerProps> = ({
<div className={`flex justify-center ${className}`}> <div className={`flex justify-center ${className}`}>
<svg <svg
role="status" role="status"
className="text-gray-300 animate-spin fill-green-500" className="text-gray-300 dark:text-gray-700 animate-spin fill-green-500"
height={size} height={size}
width={size} width={size}
viewBox="0 0 100 101" viewBox="0 0 100 101"

View file

@ -18,7 +18,7 @@ export const TopListItem: React.FC<TopListItemProps> = ({
maxCount, maxCount,
}) => { }) => {
return ( return (
<div className="group hover:bg-gray-200 bg-gray-100 border-b border-gray-200 md:justify-around text-gray-700 md:px-2"> <div className="group bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 border-b border-gray-200 dark:border-gray-700/25 md:justify-around text-gray-700 dark:text-gray-300 md:px-2">
<div className="flex pt-2"> <div className="flex pt-2">
<div className="md:flex w-11/12"> <div className="md:flex w-11/12">
<div className={`${subTitle ? "md:w-1/2" : "md:w-full"} font-bold`}> <div className={`${subTitle ? "md:w-1/2" : "md:w-full"} font-bold`}>
@ -29,10 +29,10 @@ export const TopListItem: React.FC<TopListItemProps> = ({
<div className="w-1/12 self-center">{count}</div> <div className="w-1/12 self-center">{count}</div>
</div> </div>
{maxCount && isMaxCountValid(maxCount) && ( {maxCount && isMaxCountValid(maxCount) && (
<div className="h-1 w-full bg-gradient-to-r from-teal-200 via-green-400 to-violet-400 flex flex-row-reverse"> <div className="h-1 w-full bg-gradient-to-r from-teal-200/25 via-green-400 to-violet-400 dark:from-teal-700/25 dark:via-green-600/85 dark:to-amber-500 flex flex-row-reverse">
<div <div
style={{ width: numberToPercent(1 - count / maxCount) }} style={{ width: numberToPercent(1 - count / maxCount) }}
className="h-full group-hover:bg-gray-200 bg-gray-100" className="h-full group-hover:bg-gray-200 dark:group-hover:bg-gray-700 bg-gray-100 dark:bg-gray-800"
></div> ></div>
</div> </div>
)} )}

View file

@ -21,7 +21,7 @@ export const DateSelect: React.FC<DateSelectProps> = ({
<div> <div>
<label className="text-sm">{label}</label> <label className="text-sm">{label}</label>
<input <input
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:ring" className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white dark:bg-gray-700 border border-gray-400 hover:border-gray-500 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-200 p-2 rounded shadow leading-tight focus:outline-none focus:ring"
type="date" type="date"
value={formatDateForDateInput(value)} value={formatDateForDateInput(value)}
onChange={(e) => { onChange={(e) => {

View file

@ -38,6 +38,7 @@ module.exports = {
yellow: colors.yellow, yellow: colors.yellow,
teal: colors.teal, teal: colors.teal,
violet: colors.violet, violet: colors.violet,
amber: colors.amber,
}, },
}, },
}; };