diff --git a/README.md b/README.md index f19b7593..ae62fc1d 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,11 @@ You can follow the instructions [here](https://github.com/Yooooomi/your_spotify/ | MONGO_ENDPOINT | mongodb://mongo:27017/your_spotify | The endpoint of the Mongo database, where **mongo** is the name of your service in the compose file | | LOG_LEVEL | info | The log level, debug is useful if you encouter any bugs | | CORS | _not defined_ | List of comma-separated origin allowed | -| COOKIE_VALIDITY_MS | 1h | Validity time of the authentication cookie, following [this pattern](https://github.com/vercel/ms) | +| COOKIE_VALIDITY_MS | 1h | Validity time of the authentication cookie, following [this pattern](https://github.com/vercel/ms) | | MAX_IMPORT_CACHE_SIZE | Infinite | The maximum element in the cache when importing data from an outside source, more cache means less requests to Spotify, resulting in faster imports | | MONGO_NO_ADMIN_RIGHTS | false | Do not ask for admin right on the Mongo database | | PORT | 8080 | The port of the server, **do not** modify if you're using docker | +| FRAME_ANCESTORS | _not defined_ | Sites allowed to frame the website, comma separated list of URLs (`i-want-a-security-vulnerability-and-want-to-allow-all-frame-ancestors` to allow every website) | ## CORS diff --git a/apps/client/package.json b/apps/client/package.json index bf1b7fa5..5be93ad1 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,13 +1,14 @@ { "name": "@your_spotify/client", - "version": "1.9.1", + "version": "1.10.0", "private": true, "scripts": { "start": "DISABLE_ESLINT_PLUGIN=true react-scripts start", "build": "DISABLE_ESLINT_PLUGIN=true react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "lint": "eslint src/ --ext .tsx,.ts" + "lint": "eslint src/ --ext .tsx,.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { "@emotion/react": "11.11.4", diff --git a/apps/client/public/index.html b/apps/client/public/index.html index 04433d37..67e66541 100644 --- a/apps/client/public/index.html +++ b/apps/client/public/index.html @@ -12,7 +12,7 @@ Restricting connect-src is done at start of the client server. --> - + diff --git a/apps/client/scripts/run/variables.sh b/apps/client/scripts/run/variables.sh index 76f8e05c..2059f883 100644 --- a/apps/client/scripts/run/variables.sh +++ b/apps/client/scripts/run/variables.sh @@ -35,3 +35,18 @@ then fi sed -i "s#connect-src \(.*\);#connect-src 'self' $CSP_CONNECT_SRC;#g" "$VAR_PATH/index.html" + +# Handling frame-ancestors preferences +SERVE_CONFIG_PATH="/app/apps/client/scripts/run/serve.json" + +if [[ -z "$FRAME_ANCESTORS" ]] +then + FRAME_ANCESTORS="'none'" +elif [[ "$FRAME_ANCESTORS" == "i-want-a-security-vulnerability-and-want-to-allow-all-frame-ancestors" ]] +then + FRAME_ANCESTORS="*" +else + FRAME_ANCESTORS=${FRAME_ANCESTORS//,/ } +fi + +sed -i "s#frame-ancestors \(.*\);#frame-ancestors $FRAME_ANCESTORS;#g" "$SERVE_CONFIG_PATH" diff --git a/apps/client/src/components/Header/Header.tsx b/apps/client/src/components/Header/Header.tsx index a5160d3a..1fda9e29 100644 --- a/apps/client/src/components/Header/Header.tsx +++ b/apps/client/src/components/Header/Header.tsx @@ -7,7 +7,7 @@ import { setDataInterval } from "../../services/redux/modules/user/reducer"; import { selectIntervalDetail } from "../../services/redux/modules/user/selector"; import { intervalDetailToRedux } from "../../services/redux/modules/user/utils"; import { useAppDispatch } from "../../services/redux/tools"; -import IntervalSelector from "../IntervalSelector"; +import { IntervalSelector } from "../IntervalSelector"; import Text from "../Text"; import { LayoutContext } from "../Layout/LayoutContext"; import { useSider } from "../Layout/useSider"; diff --git a/apps/client/src/components/History/Track/Track.tsx b/apps/client/src/components/History/Track/Track.tsx index 18dde1b9..bfbbefae 100644 --- a/apps/client/src/components/History/Track/Track.tsx +++ b/apps/client/src/components/History/Track/Track.tsx @@ -1,21 +1,19 @@ import { Fragment, useMemo } from "react"; import clsx from "clsx"; -import { - dateToListenedAt, - msToMinutesAndSeconds, -} from '../../../services/stats'; -import { Album, Artist, Track as TrackType } from '../../../services/types'; -import s from './index.module.css'; -import InlineArtist from '../../InlineArtist'; -import Text from '../../Text'; -import TrackOptions from '../../TrackOptions'; -import InlineTrack from '../../InlineTrack'; -import { ColumnDescription, GridRowWrapper } from '../../Grid'; -import PlayButton from '../../PlayButton'; -import { useMobile } from '../../../services/hooks/hooks'; -import { trackGrid } from './TrackGrid'; -import LongClickableTrack from '../../LongClickableTrack'; -import InlineAlbum from '../../InlineAlbum'; +import { msToMinutesAndSeconds } from "../../../services/stats"; +import { Album, Artist, Track as TrackType } from "../../../services/types"; +import InlineArtist from "../../InlineArtist"; +import Text from "../../Text"; +import TrackOptions from "../../TrackOptions"; +import InlineTrack from "../../InlineTrack"; +import { ColumnDescription, GridRowWrapper } from "../../Grid"; +import PlayButton from "../../PlayButton"; +import { useMobile } from "../../../services/hooks/hooks"; +import LongClickableTrack from "../../LongClickableTrack"; +import InlineAlbum from "../../InlineAlbum"; +import { DateFormatter } from "../../../services/date"; +import { trackGrid } from "./TrackGrid"; +import s from "./index.module.css"; interface TrackProps { listenedAt?: Date; @@ -67,7 +65,7 @@ export default function Track({ { ...trackGrid.listened, node: listenedAt && !isMobile && ( - {dateToListenedAt(listenedAt)} + {DateFormatter.listenedAt(listenedAt)} ), }, { @@ -75,7 +73,7 @@ export default function Track({ node: !isMobile && , }, ], - [album.images, album.name, artists, isMobile, isTablet, listenedAt, track], + [album, artists, isMobile, isTablet, listenedAt, track], ); return ( diff --git a/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx b/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx index 1cdcc46c..7aa3d4db 100644 --- a/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx +++ b/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx @@ -12,6 +12,7 @@ import Tooltip from "../../Tooltip"; import { TitleFormatter, ValueFormatter } from "../../Tooltip/Tooltip"; import LoadingImplementedChart from "../LoadingImplementedChart"; import { ImplementedChartProps } from "../types"; +import { DateFormatter } from "../../../services/date"; interface BestOfHourProps extends ImplementedChartProps {} @@ -61,10 +62,6 @@ function getElementData( ); } -function formatX(value: any) { - return `${value}:00`; -} - export default function BestOfHour({ className }: BestOfHourProps) { const { interval } = useSelector(selectRawIntervalDetail); const [element, setElement] = useState(Element.ARTIST); @@ -80,7 +77,8 @@ export default function BestOfHour({ className }: BestOfHourProps) { }, [result]); const tooltipTitle = useCallback>( - ({ x }) => `20 most listened ${element} at ${x}:00`, + ({ x }) => + `20 most listened ${element} at ${DateFormatter.fromNumberToHour(x)}`, [element], ); @@ -130,7 +128,7 @@ export default function BestOfHour({ className }: BestOfHourProps) { className={className}> } /> diff --git a/apps/client/src/components/ImplementedCharts/ListeningRepartition/ListeningRepartition.tsx b/apps/client/src/components/ImplementedCharts/ListeningRepartition/ListeningRepartition.tsx index cd38172d..f143973a 100644 --- a/apps/client/src/components/ImplementedCharts/ListeningRepartition/ListeningRepartition.tsx +++ b/apps/client/src/components/ImplementedCharts/ListeningRepartition/ListeningRepartition.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { useSelector } from "react-redux"; import { api } from "../../../services/apis/api"; import { useAPI } from "../../../services/hooks/hooks"; @@ -9,14 +9,14 @@ import LoadingImplementedChart from "../LoadingImplementedChart"; import { selectRawIntervalDetail } from "../../../services/redux/modules/user/selector"; import Tooltip from "../../Tooltip"; import { TitleFormatter, ValueFormatter } from "../../Tooltip/Tooltip"; +import { DateFormatter } from "../../../services/date"; interface ListeningRepartitionProps extends ImplementedChartProps {} -const formatXAxis = (value: any) => `${value}:00`; - const formatYAxis = (value: any) => `${value}%`; -const tooltipTitle: TitleFormatter = ({ x }) => `${x}:00`; +const tooltipTitle: TitleFormatter = ({ x }) => + DateFormatter.fromNumberToHour(x); export default function ListeningRepartition({ className, @@ -72,7 +72,7 @@ export default function ListeningRepartition({ } /> diff --git a/apps/client/src/components/IntervalSelector/IntervalSelector.tsx b/apps/client/src/components/IntervalSelector/IntervalSelector.tsx index 08d53780..4a438f73 100644 --- a/apps/client/src/components/IntervalSelector/IntervalSelector.tsx +++ b/apps/client/src/components/IntervalSelector/IntervalSelector.tsx @@ -10,11 +10,8 @@ import { Select, } from "@mui/material"; import React, { useState, useCallback, useMemo } from "react"; -import { - startOfDay, - getAppropriateTimesplitFromRange, - endOfDay, -} from "../../services/date"; +import { endOfDay, startOfDay } from "date-fns"; +import { getAppropriateTimesplitFromRange } from "../../services/date"; import { useMobile } from "../../services/hooks/hooks"; import { allIntervals, @@ -34,7 +31,7 @@ interface IntervalSelectorProps { forceTiny?: boolean; } -export default function IntervalSelector({ +export function IntervalSelector({ value, onChange, selectType, diff --git a/apps/client/src/components/IntervalSelector/index.ts b/apps/client/src/components/IntervalSelector/index.ts index 6988c644..3d786366 100644 --- a/apps/client/src/components/IntervalSelector/index.ts +++ b/apps/client/src/components/IntervalSelector/index.ts @@ -1 +1 @@ -export { default } from "./IntervalSelector"; +export * from "./IntervalSelector"; diff --git a/apps/client/src/scenes/AlbumStats/AlbumRank/AlbumRank.tsx b/apps/client/src/scenes/AlbumStats/AlbumRank/AlbumRank.tsx index cc1b5bda..da9c4f3c 100644 --- a/apps/client/src/scenes/AlbumStats/AlbumRank/AlbumRank.tsx +++ b/apps/client/src/scenes/AlbumStats/AlbumRank/AlbumRank.tsx @@ -1,12 +1,12 @@ -import { CircularProgress } from '@mui/material'; -import clsx from 'clsx'; -import { useCallback, useMemo } from 'react'; -import InlineAlbum from '../../../components/InlineAlbum'; -import Text from '../../../components/Text'; -import { api } from '../../../services/apis/api'; -import { useLoadAlbums } from '../../../services/hooks/artist'; -import { useAPI } from '../../../services/hooks/hooks'; -import s from './index.module.css'; +import { CircularProgress } from "@mui/material"; +import clsx from "clsx"; +import { useCallback, useMemo } from "react"; +import InlineAlbum from "../../../components/InlineAlbum"; +import Text from "../../../components/Text"; +import { api } from "../../../services/apis/api"; +import { useLoadAlbums } from "../../../services/hooks/artist"; +import { useAPI } from "../../../services/hooks/hooks"; +import s from "./index.module.css"; interface AlbumRankProps { albumId: string; @@ -22,7 +22,6 @@ export default function AlbumRank({ albumId }: AlbumRankProps) { const { albums, loaded } = useLoadAlbums(ids); const getArtist = useCallback((id: string) => albums[id], [albums]); - console.log(albumRank, loaded); if (!albumRank || !loaded) { return (
@@ -53,7 +52,7 @@ export default function AlbumRank({ albumId }: AlbumRankProps) { {albumRank.index + k + (albumRank.isMax ? 1 : 0) + - (albumRank.isMin ? -1 : 0)}{' '} + (albumRank.isMin ? -1 : 0)}{" "}
))} diff --git a/apps/client/src/scenes/AlbumStats/AlbumStats.tsx b/apps/client/src/scenes/AlbumStats/AlbumStats.tsx index d01ea91c..a76bdc47 100644 --- a/apps/client/src/scenes/AlbumStats/AlbumStats.tsx +++ b/apps/client/src/scenes/AlbumStats/AlbumStats.tsx @@ -1,17 +1,17 @@ -import { CircularProgress, Grid } from '@mui/material'; -import { TimelapseOutlined } from '@mui/icons-material'; -import Header from '../../components/Header'; -import { AlbumStatsResponse } from '../../services/apis/api'; -import s from './index.module.css'; -import InlineArtist from '../../components/InlineArtist'; -import IdealImage from '../../components/IdealImage'; -import FirstAndLast from '../ArtistStats/FirstAndLast'; -import InlineTrack from '../../components/InlineTrack'; -import TitleCard from '../../components/TitleCard'; -import Text from '../../components/Text'; -import ImageTwoLines from '../../components/ImageTwoLines'; -import { msToMinutesAndSeconds } from '../../services/stats'; -import AlbumRank from './AlbumRank'; +import { CircularProgress, Grid } from "@mui/material"; +import { TimelapseOutlined } from "@mui/icons-material"; +import Header from "../../components/Header"; +import { AlbumStatsResponse } from "../../services/apis/api"; +import InlineArtist from "../../components/InlineArtist"; +import IdealImage from "../../components/IdealImage"; +import FirstAndLast from "../ArtistStats/FirstAndLast"; +import InlineTrack from "../../components/InlineTrack"; +import TitleCard from "../../components/TitleCard"; +import Text from "../../components/Text"; +import ImageTwoLines from "../../components/ImageTwoLines"; +import { msToMinutesAndSeconds } from "../../services/stats"; +import s from "./index.module.css"; +import AlbumRank from "./AlbumRank"; interface AlbumStatsProps { stats: AlbumStatsResponse; @@ -37,7 +37,7 @@ export default function AlbumStats({ stats }: AlbumStatsProps) { subtitle={stats.artists.map((artist, k) => ( <> - {k < stats.artists.length - 1 && ', '} + {k < stats.artists.length - 1 && ", "} ))} hideInterval diff --git a/apps/client/src/scenes/ArtistStats/ArtistStats.tsx b/apps/client/src/scenes/ArtistStats/ArtistStats.tsx index 5a58233d..ec64f7a1 100644 --- a/apps/client/src/scenes/ArtistStats/ArtistStats.tsx +++ b/apps/client/src/scenes/ArtistStats/ArtistStats.tsx @@ -1,20 +1,21 @@ -import { CircularProgress, Grid } from '@mui/material'; -import { useSelector } from 'react-redux'; -import Header from '../../components/Header'; -import TitleCard from '../../components/TitleCard'; -import { ArtistStatsResponse } from '../../services/apis/api'; -import { buildFromDateId, dateToMonthAndYear } from '../../services/stats'; -import s from './index.module.css'; -import DayRepartition from './DayRepartition'; -import Text from '../../components/Text'; -import ArtistRank from './ArtistRank/ArtistRank'; -import InlineTrack from '../../components/InlineTrack'; -import FirstAndLast from './FirstAndLast'; -import ArtistContextMenu from './ArtistContextMenu'; -import { selectBlacklistedArtist } from '../../services/redux/modules/user/selector'; -import IdealImage from '../../components/IdealImage'; -import ImageTwoLines from '../../components/ImageTwoLines'; -import InlineAlbum from '../../components/InlineAlbum'; +import { CircularProgress, Grid } from "@mui/material"; +import { useSelector } from "react-redux"; +import Header from "../../components/Header"; +import TitleCard from "../../components/TitleCard"; +import { ArtistStatsResponse } from "../../services/apis/api"; +import { buildFromDateId } from "../../services/stats"; +import Text from "../../components/Text"; +import InlineTrack from "../../components/InlineTrack"; +import { selectBlacklistedArtist } from "../../services/redux/modules/user/selector"; +import IdealImage from "../../components/IdealImage"; +import ImageTwoLines from "../../components/ImageTwoLines"; +import InlineAlbum from "../../components/InlineAlbum"; +import { DateFormatter } from "../../services/date"; +import ArtistContextMenu from "./ArtistContextMenu"; +import FirstAndLast from "./FirstAndLast"; +import ArtistRank from "./ArtistRank/ArtistRank"; +import DayRepartition from "./DayRepartition"; +import s from "./index.module.css"; interface ArtistStatsProps { artistId: string; @@ -95,7 +96,9 @@ export default function ArtistStats({ artistId, stats }: ArtistStatsProps) { {bestPeriod && (
- {dateToMonthAndYear(buildFromDateId(bestPeriod._id))} + {DateFormatter.toMonthStringYear( + buildFromDateId(bestPeriod._id), + )} {bestPeriod.count} times ( @@ -107,7 +110,7 @@ export default function ArtistStats({ artistId, stats }: ArtistStatsProps) { {secondBestPeriod && (
- {dateToMonthAndYear( + {DateFormatter.toMonthStringYear( buildFromDateId(secondBestPeriod._id), )} diff --git a/apps/client/src/scenes/ArtistStats/FirstAndLast/FirstAndLast.tsx b/apps/client/src/scenes/ArtistStats/FirstAndLast/FirstAndLast.tsx index 7571b2b7..94c77f5a 100644 --- a/apps/client/src/scenes/ArtistStats/FirstAndLast/FirstAndLast.tsx +++ b/apps/client/src/scenes/ArtistStats/FirstAndLast/FirstAndLast.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; -import { dateToListenedAt } from "../../../services/stats"; import { SpotifyImage } from "../../../services/types"; import TitleCard from "../../../components/TitleCard"; import IdealImage from "../../../components/IdealImage"; import ImageTwoLines from "../../../components/ImageTwoLines"; +import { DateFormatter } from "../../../services/date"; import s from "./index.module.css"; interface FirstAndLastProps { @@ -36,7 +36,7 @@ export default function FirstAndLast({ /> } first={lastElement} - second={`Last listened on ${dateToListenedAt(new Date(lastDate))}`} + second={`Last listened on ${DateFormatter.listenedAt(new Date(lastDate))}`} />
@@ -50,7 +50,7 @@ export default function FirstAndLast({ /> } first={firstElement} - second={`First listened on ${dateToListenedAt(new Date(firstDate))}`} + second={`First listened on ${DateFormatter.listenedAt(new Date(firstDate))}`} />
diff --git a/apps/client/src/scenes/Collaborative/Affinity/Affinity.tsx b/apps/client/src/scenes/Collaborative/Affinity/Affinity.tsx index fbb11a97..42ddc097 100644 --- a/apps/client/src/scenes/Collaborative/Affinity/Affinity.tsx +++ b/apps/client/src/scenes/Collaborative/Affinity/Affinity.tsx @@ -6,7 +6,7 @@ import { AdminAccount } from "../../../services/redux/modules/admin/reducer"; import { selectAccounts } from "../../../services/redux/modules/admin/selector"; import { CollaborativeMode } from "../../../services/types"; import { selectUser } from "../../../services/redux/modules/user/selector"; -import IntervalSelector from "../../../components/IntervalSelector"; +import { IntervalSelector } from "../../../components/IntervalSelector"; import Text from "../../../components/Text"; import { detailIntervalToQuery, diff --git a/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx b/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx index c78386b9..33014250 100644 --- a/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx +++ b/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx @@ -7,9 +7,11 @@ import InlineArtist from "../../../components/InlineArtist"; import InlineTrack from "../../../components/InlineTrack"; import PlayButton from "../../../components/PlayButton"; import Text from "../../../components/Text"; -import { intervalToHoursAndMinutes } from "../../../services/date"; +import { + DateFormatter, + intervalToHoursAndMinutes, +} from "../../../services/date"; import { useLoadArtists } from "../../../services/hooks/artist"; -import { dateToListenedAt } from "../../../services/stats"; import { Track, TrackInfo } from "../../../services/types"; import s from "./index.module.css"; @@ -60,8 +62,8 @@ export default function LongestSession({ onChange={(_, value) => setExpanded(value)}> }> - {dateToListenedAt(new Date(firstTrack.played_at))} for {totalDuration}{" "} - ({tracks.length} songs) + {DateFormatter.listenedAt(new Date(firstTrack.played_at))} for{" "} + {totalDuration} ({tracks.length} songs) diff --git a/apps/client/src/scenes/LongestSessions/LongestSessions.tsx b/apps/client/src/scenes/LongestSessions/LongestSessions.tsx index 33199198..f1156bd2 100644 --- a/apps/client/src/scenes/LongestSessions/LongestSessions.tsx +++ b/apps/client/src/scenes/LongestSessions/LongestSessions.tsx @@ -18,7 +18,6 @@ export default function LongestSessions() { ); const hasValidSessions = validResults && validResults.length > 0; - console.log(validResults); return (
diff --git a/apps/client/src/scenes/Settings/DateFormat/DateFormat.tsx b/apps/client/src/scenes/Settings/DateFormat/DateFormat.tsx new file mode 100644 index 00000000..95d7866c --- /dev/null +++ b/apps/client/src/scenes/Settings/DateFormat/DateFormat.tsx @@ -0,0 +1,49 @@ +import { Select, MenuItem } from "@mui/material"; +import { useCallback } from "react"; +import { useSelector } from "react-redux"; +import Text from "../../../components/Text"; +import TitleCard from "../../../components/TitleCard"; +import { changeDateFormat } from "../../../services/redux/modules/settings/thunk"; +import { selectDateFormat } from "../../../services/redux/modules/user/selector"; +import { useAppDispatch } from "../../../services/redux/tools"; +import SettingLine from "../SettingLine"; +import { dateFormats } from "./dateFormats"; +import s from "./index.module.css"; + +export default function DateFormat() { + const dispatch = useAppDispatch(); + const currentDateFormat = useSelector(selectDateFormat); + + const handleChangeDateFormat = useCallback( + (newDateFormat: string | null | undefined) => { + dispatch(changeDateFormat(newDateFormat ?? "default")).catch( + console.error, + ); + }, + [dispatch], + ); + + return ( + + + Format of dates throughout the application for this user. + + handleChangeDateFormat(ev.target.value)}> + Follow browser + {dateFormats.map(dateFormat => ( + + {dateFormat.name} + + ))} + + } + /> + + ); +} diff --git a/apps/client/src/scenes/Settings/DateFormat/dateFormats.ts b/apps/client/src/scenes/Settings/DateFormat/dateFormats.ts new file mode 100644 index 00000000..9b86cc63 --- /dev/null +++ b/apps/client/src/scenes/Settings/DateFormat/dateFormats.ts @@ -0,0 +1,144 @@ +export const dateFormats = [ + { name: "Afrikaans", code: "af" }, + { name: "Albanian - shqip", code: "sq" }, + { name: "Amharic - አማርኛ", code: "am" }, + { name: "Arabic - العربية", code: "ar" }, + { name: "Aragonese - aragonés", code: "an" }, + { name: "Armenian - հայերեն", code: "hy" }, + { name: "Asturian - asturianu", code: "ast" }, + { name: "Azerbaijani - azərbaycan dili", code: "az" }, + { name: "Basque - euskara", code: "eu" }, + { name: "Belarusian - беларуская", code: "be" }, + { name: "Bengali - বাংলা", code: "bn" }, + { name: "Bosnian - bosanski", code: "bs" }, + { name: "Breton - brezhoneg", code: "br" }, + { name: "Bulgarian - български", code: "bg" }, + { name: "Catalan - català", code: "ca" }, + { name: "Central Kurdish - کوردی (دەستنوسی عەرەبی)", code: "ckb" }, + { name: "Chinese - 中文", code: "zh" }, + { name: "Chinese (Hong Kong) - 中文(香港)", code: "zh-HK" }, + { name: "Chinese (Simplified) - 中文(简体)", code: "zh-CN" }, + { name: "Chinese (Traditional) - 中文(繁體)", code: "zh-TW" }, + { name: "Corsican", code: "co" }, + { name: "Croatian - hrvatski", code: "hr" }, + { name: "Czech - čeština", code: "cs" }, + { name: "Danish - dansk", code: "da" }, + { name: "Dutch - Nederlands", code: "nl" }, + { name: "English", code: "en" }, + { name: "English (Australia)", code: "en-AU" }, + { name: "English (Canada)", code: "en-CA" }, + { name: "English (India)", code: "en-IN" }, + { name: "English (New Zealand)", code: "en-NZ" }, + { name: "English (South Africa)", code: "en-ZA" }, + { name: "English (United Kingdom)", code: "en-GB" }, + { name: "English (United States)", code: "en-US" }, + { name: "Esperanto - esperanto", code: "eo" }, + { name: "Estonian - eesti", code: "et" }, + { name: "Faroese - føroyskt", code: "fo" }, + { name: "Filipino", code: "fil" }, + { name: "Finnish - suomi", code: "fi" }, + { name: "French - français", code: "fr" }, + { name: "French (Canada) - français (Canada)", code: "fr-CA" }, + { name: "French (France) - français (France)", code: "fr-FR" }, + { name: "French (Switzerland) - français (Suisse)", code: "fr-CH" }, + { name: "Galician - galego", code: "gl" }, + { name: "Georgian - ქართული", code: "ka" }, + { name: "German - Deutsch", code: "de" }, + { name: "German (Austria) - Deutsch (Österreich)", code: "de-AT" }, + { name: "German (Germany) - Deutsch (Deutschland)", code: "de-DE" }, + { name: "German (Liechtenstein) - Deutsch (Liechtenstein)", code: "de-LI" }, + { name: "German (Switzerland) - Deutsch (Schweiz)", code: "de-CH" }, + { name: "Greek - Ελληνικά", code: "el" }, + { name: "Guarani", code: "gn" }, + { name: "Gujarati - ગુજરાતી", code: "gu" }, + { name: "Hausa", code: "ha" }, + { name: "Hawaiian - ʻŌlelo Hawaiʻi", code: "haw" }, + { name: "Hebrew - עברית", code: "he" }, + { name: "Hindi - हिन्दी", code: "hi" }, + { name: "Hungarian - magyar", code: "hu" }, + { name: "Icelandic - íslenska", code: "is" }, + { name: "Indonesian - Indonesia", code: "id" }, + { name: "Interlingua", code: "ia" }, + { name: "Irish - Gaeilge", code: "ga" }, + { name: "Italian - italiano", code: "it" }, + { name: "Italian (Italy) - italiano (Italia)", code: "it-IT" }, + { name: "Italian (Switzerland) - italiano (Svizzera)", code: "it-CH" }, + { name: "Japanese - 日本語", code: "ja" }, + { name: "Kannada - ಕನ್ನಡ", code: "kn" }, + { name: "Kazakh - қазақ тілі", code: "kk" }, + { name: "Khmer - ខ្មែរ", code: "km" }, + { name: "Korean - 한국어", code: "ko" }, + { name: "Kurdish - Kurdî", code: "ku" }, + { name: "Kyrgyz - кыргызча", code: "ky" }, + { name: "Lao - ລາວ", code: "lo" }, + { name: "Latin", code: "la" }, + { name: "Latvian - latviešu", code: "lv" }, + { name: "Lingala - lingála", code: "ln" }, + { name: "Lithuanian - lietuvių", code: "lt" }, + { name: "Macedonian - македонски", code: "mk" }, + { name: "Malay - Bahasa Melayu", code: "ms" }, + { name: "Malayalam - മലയാളം", code: "ml" }, + { name: "Maltese - Malti", code: "mt" }, + { name: "Marathi - मराठी", code: "mr" }, + { name: "Mongolian - монгол", code: "mn" }, + { name: "Nepali - नेपाली", code: "ne" }, + { name: "Norwegian - norsk", code: "no" }, + { name: "Norwegian Bokmål - norsk bokmål", code: "nb" }, + { name: "Norwegian Nynorsk - nynorsk", code: "nn" }, + { name: "Occitan", code: "oc" }, + { name: "Oriya - ଓଡ଼ିଆ", code: "or" }, + { name: "Oromo - Oromoo", code: "om" }, + { name: "Pashto - پښتو", code: "ps" }, + { name: "Persian - فارسی", code: "fa" }, + { name: "Polish - polski", code: "pl" }, + { name: "Portuguese - português", code: "pt" }, + { name: "Portuguese (Brazil) - português (Brasil)", code: "pt-BR" }, + { name: "Portuguese (Portugal) - português (Portugal)", code: "pt-PT" }, + { name: "Punjabi - ਪੰਜਾਬੀ", code: "pa" }, + { name: "Quechua", code: "qu" }, + { name: "Romanian - română", code: "ro" }, + { name: "Romanian (Moldova) - română (Moldova)", code: "mo" }, + { name: "Romansh - rumantsch", code: "rm" }, + { name: "Russian - русский", code: "ru" }, + { name: "Scottish Gaelic", code: "gd" }, + { name: "Serbian - српски", code: "sr" }, + { name: "Serbo - Croatian", code: "sh" }, + { name: "Shona - chiShona", code: "sn" }, + { name: "Sindhi", code: "sd" }, + { name: "Sinhala - සිංහල", code: "si" }, + { name: "Slovak - slovenčina", code: "sk" }, + { name: "Slovenian - slovenščina", code: "sl" }, + { name: "Somali - Soomaali", code: "so" }, + { name: "Southern Sotho", code: "st" }, + { name: "Spanish - español", code: "es" }, + { name: "Spanish (Argentina) - español (Argentina)", code: "es-AR" }, + { name: "Spanish (Latin America) - español (Latinoamérica)", code: "es-419" }, + { name: "Spanish (Mexico) - español (México)", code: "es-MX" }, + { name: "Spanish (Spain) - español (España)", code: "es-ES" }, + { name: "Spanish (United States) - español (Estados Unidos)", code: "es-US" }, + { name: "Sundanese", code: "su" }, + { name: "Swahili - Kiswahili", code: "sw" }, + { name: "Swedish - svenska", code: "sv" }, + { name: "Tajik - тоҷикӣ", code: "tg" }, + { name: "Tamil - தமிழ்", code: "ta" }, + { name: "Tatar", code: "tt" }, + { name: "Telugu - తెలుగు", code: "te" }, + { name: "Thai - ไทย", code: "th" }, + { name: "Tigrinya - ትግርኛ", code: "ti" }, + { name: "Tongan - lea fakatonga", code: "to" }, + { name: "Turkish - Türkçe", code: "tr" }, + { name: "Turkmen", code: "tk" }, + { name: "Twi", code: "tw" }, + { name: "Ukrainian - українська", code: "uk" }, + { name: "Urdu - اردو", code: "ur" }, + { name: "Uyghur", code: "ug" }, + { name: "Uzbek - o‘zbek", code: "uz" }, + { name: "Vietnamese - Tiếng Việt", code: "vi" }, + { name: "Walloon - wa", code: "wa" }, + { name: "Welsh - Cymraeg", code: "cy" }, + { name: "Western Frisian", code: "fy" }, + { name: "Xhosa", code: "xh" }, + { name: "Yiddish", code: "yi" }, + { name: "Yoruba - Èdè Yorùbá", code: "yo" }, + { name: "Zulu - isiZulu", code: "zu" }, +]; diff --git a/apps/client/src/scenes/Settings/DateFormat/index.module.css b/apps/client/src/scenes/Settings/DateFormat/index.module.css new file mode 100644 index 00000000..9c929669 --- /dev/null +++ b/apps/client/src/scenes/Settings/DateFormat/index.module.css @@ -0,0 +1,4 @@ +.marginbottom { + display: block; + margin-bottom: 8px; +} \ No newline at end of file diff --git a/apps/client/src/scenes/Settings/DateFormat/index.ts b/apps/client/src/scenes/Settings/DateFormat/index.ts new file mode 100644 index 00000000..ad137ece --- /dev/null +++ b/apps/client/src/scenes/Settings/DateFormat/index.ts @@ -0,0 +1 @@ +export { default } from "./DateFormat"; diff --git a/apps/client/src/scenes/Settings/Importer/ImportHistory/ImportHistory.tsx b/apps/client/src/scenes/Settings/Importer/ImportHistory/ImportHistory.tsx index 63a090c6..78d8fb11 100644 --- a/apps/client/src/scenes/Settings/Importer/ImportHistory/ImportHistory.tsx +++ b/apps/client/src/scenes/Settings/Importer/ImportHistory/ImportHistory.tsx @@ -1,7 +1,6 @@ import { useCallback } from "react"; import { useSelector } from "react-redux"; import { CircularProgress } from "@mui/material"; -import { dateToListenedAt } from "../../../../services/stats"; import SettingLine from "../../SettingLine"; import { selectImportStates } from "../../../../services/redux/modules/import/selector"; import { @@ -13,6 +12,7 @@ import { compact } from "../../../../services/tools"; import { ImporterStateStatus } from "../../../../services/redux/modules/import/types"; import Text from "../../../../components/Text"; import { useAppDispatch } from "../../../../services/redux/tools"; +import { DateFormatter } from "../../../../services/date"; import s from "./index.module.css"; const statusToString: Record = { @@ -28,7 +28,7 @@ export default function ImportHistory() { const cleanImport = useCallback( async (id: string) => { - dispatch(cleanupImport(id)); + dispatch(cleanupImport(id)).catch(console.error); }, [dispatch], ); @@ -52,7 +52,7 @@ export default function ImportHistory() { key={st._id} left={ - Import of {dateToListenedAt(new Date(st.createdAt))} + Import of {DateFormatter.listenedAt(new Date(st.createdAt))} from {st.type} } diff --git a/apps/client/src/scenes/Settings/Settings.tsx b/apps/client/src/scenes/Settings/Settings.tsx index a0e5a749..c55f9df1 100644 --- a/apps/client/src/scenes/Settings/Settings.tsx +++ b/apps/client/src/scenes/Settings/Settings.tsx @@ -26,6 +26,7 @@ import RelogToSpotify from "./RelogToSpotify"; import SetAdmin from "./SetAdmin"; import SpotifyAccountInfos from "./SpotifyAccountInfos"; import Timezone from "./Timezone"; +import DateFormat from "./DateFormat"; export default function Settings() { const settings = useSelector(selectSettings); @@ -107,6 +108,7 @@ export default function Settings() { {!isPublic && } {!isPublic && } + {!isPublic && } } /> diff --git a/apps/client/src/scenes/Settings/Timezone/Timezone.tsx b/apps/client/src/scenes/Settings/Timezone/Timezone.tsx index 797ce865..e1ce04c5 100644 --- a/apps/client/src/scenes/Settings/Timezone/Timezone.tsx +++ b/apps/client/src/scenes/Settings/Timezone/Timezone.tsx @@ -12,14 +12,14 @@ import s from "./index.module.css"; export default function Timezone() { const dispatch = useAppDispatch(); - const dark = useSelector(selectTimezone); + const currentTimezone = useSelector(selectTimezone); const handleChangeTimezone = useCallback( (newTimezone: string | null | undefined) => { if (newTimezone === "follow") { newTimezone = null; } - dispatch(changeTimezone(newTimezone)); + dispatch(changeTimezone(newTimezone)).catch(console.error); }, [dispatch], ); @@ -35,7 +35,7 @@ export default function Timezone() { right={