diff --git a/src/components/Dialogs/EditMediaDialog/Chapters.jsx b/src/components/Dialogs/EditMediaDialog/Chapters.tsx similarity index 69% rename from src/components/Dialogs/EditMediaDialog/Chapters.jsx rename to src/components/Dialogs/EditMediaDialog/Chapters.tsx index 0b6605ec7..aa1cfbf51 100644 --- a/src/components/Dialogs/EditMediaDialog/Chapters.jsx +++ b/src/components/Dialogs/EditMediaDialog/Chapters.tsx @@ -1,23 +1,36 @@ import clsx from 'clsx'; -import React from 'react'; -import PropTypes from 'prop-types'; import formatDuration from 'format-duration'; import { useTranslator } from '@u-wave/react-translate'; import MenuItem from '@mui/material/MenuItem'; import Select from '../../Form/Select'; +type Chapter = { + start: number, + end: number, + title: string, +}; +type ChaptersProps = { + className?: string, + available: Chapter[], + start: number, + end: number, + tabIndex: number, + onChange: (value: Chapter) => void, +}; function Chapters({ className, available, start, end, + tabIndex, onChange, - ...props -}) { +}: ChaptersProps) { const { t } = useTranslator(); - const handleChange = (event) => { - const newIndex = event.target.value; - onChange(available[newIndex]); + const handleChange = (event: React.FormEvent<{ value: number }>) => { + const newIndex = event.currentTarget.value; + if (available[newIndex] != null) { + onChange(available[newIndex]); + } }; const selectedIndex = available.findIndex( @@ -32,7 +45,7 @@ function Chapters({ }} onChange={handleChange} value={selectedIndex} - {...props} + tabIndex={tabIndex} > @@ -53,16 +66,4 @@ function Chapters({ ); } -Chapters.propTypes = { - className: PropTypes.string, - available: PropTypes.arrayOf(PropTypes.shape({ - start: PropTypes.number.isRequired, - end: PropTypes.number.isRequired, - title: PropTypes.string.isRequired, - })).isRequired, - start: PropTypes.number.isRequired, - end: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, -}; - export default Chapters; diff --git a/src/components/Dialogs/EditMediaDialog/index.jsx b/src/components/Dialogs/EditMediaDialog/index.tsx similarity index 78% rename from src/components/Dialogs/EditMediaDialog/index.jsx rename to src/components/Dialogs/EditMediaDialog/index.tsx index d1ae7816e..172097b1d 100644 --- a/src/components/Dialogs/EditMediaDialog/index.jsx +++ b/src/components/Dialogs/EditMediaDialog/index.tsx @@ -1,6 +1,5 @@ import cx from 'clsx'; -import React from 'react'; -import PropTypes from 'prop-types'; +import { useCallback, useId, useState } from 'react'; import { useTranslator } from '@u-wave/react-translate'; import formatDuration from 'format-duration'; import Dialog from '@mui/material/Dialog'; @@ -22,20 +21,49 @@ import FormGroup from '../../Form/Group'; import Button from '../../Form/Button'; import TextField from '../../Form/TextField'; import Chapters from './Chapters'; - -const { - useCallback, - useId, - useState, -} = React; +import type { Media } from '../../../reducers/booth'; // naive HH:mm:ss → seconds -const parseDuration = (str) => str.split(':') - .map((part) => parseInt(part.trim(), 10)) - .reduce((duration, part) => (duration * 60) + part, 0); +function parseDuration(str: string) { + return str.split(':') + .map((part) => parseInt(part.trim(), 10)) + .reduce((duration, part) => (duration * 60) + part, 0); +} const BASE_TAB_INDEX = 1000; +type Chapter = { + start: number, + end: number, + title: string, +}; + +function hasChapters( + sourceData: Record | null, +): sourceData is { chapters: Chapter[] } { + return sourceData != null + && Array.isArray(sourceData.chapters) + && sourceData.chapters.every((chapter) => ( + typeof chapter === 'object' + && chapter != null + && 'start' in chapter + && typeof chapter.start === 'number' + && 'end' in chapter + && typeof chapter.end === 'number' + && 'title' in chapter + && typeof chapter.title === 'string' + )); +} + +type EditMediaDialogProps = { + media: Media, + open: boolean, + bodyClassName?: string, + contentClassName?: string, + titleClassName?: string, + onEditedMedia: (update: { artist: string, title: string, start: number, end: number }) => void, + onCloseDialog: () => void, +}; function EditMediaDialog({ media, open, @@ -44,20 +72,20 @@ function EditMediaDialog({ titleClassName, onEditedMedia, onCloseDialog, -}) { +}: EditMediaDialogProps) { const { t } = useTranslator(); const id = useId(); const ariaTitle = `${id}-title`; const startFieldId = `${id}-start`; const endFieldId = `${id}-end`; - const [errors, setErrors] = useState(null); + const [errors, setErrors] = useState(null); const [artist, setArtist] = useState(media.artist); const [title, setTitle] = useState(media.title); const [start, setStart] = useState(formatDuration(media.start * 1000)); const [end, setEnd] = useState(formatDuration(media.end * 1000)); - const handleSubmit = (e) => { + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const startSeconds = parseDuration(start); @@ -89,23 +117,23 @@ function EditMediaDialog({ onCloseDialog(); }; - const handleChangeArtist = useCallback((event) => { - setArtist(event.target.value); + const handleChangeArtist = useCallback((event: React.FormEvent) => { + setArtist(event.currentTarget.value); }, []); - const handleChangeTitle = useCallback((event) => { - setTitle(event.target.value); + const handleChangeTitle = useCallback((event: React.FormEvent) => { + setTitle(event.currentTarget.value); }, []); - const handleChangeStart = useCallback((event) => { - setStart(event.target.value); + const handleChangeStart = useCallback((event: React.FormEvent) => { + setStart(event.currentTarget.value); }, []); - const handleChangeEnd = useCallback((event) => { - setEnd(event.target.value); + const handleChangeEnd = useCallback((event: React.FormEvent) => { + setEnd(event.currentTarget.value); }, []); - const handleChangeChapter = useCallback((chapter) => { + const handleChangeChapter = useCallback((chapter: Chapter) => { setStart(formatDuration(chapter.start * 1000)); setEnd(formatDuration(chapter.end * 1000)); }, []); @@ -181,8 +209,7 @@ function EditMediaDialog({ /> ); - const chapterCount = media.sourceData?.chapters?.length ?? 0; - const chapters = chapterCount > 0 ? ( + const chapters = hasChapters(media.sourceData) && media.sourceData.chapters.length > 0 ? (

{t('dialogs.editMedia.chapterLabel')} @@ -270,14 +297,4 @@ function EditMediaDialog({ ); } -EditMediaDialog.propTypes = { - open: PropTypes.bool, - media: PropTypes.object, - bodyClassName: PropTypes.string, - contentClassName: PropTypes.string, - titleClassName: PropTypes.string, - onEditedMedia: PropTypes.func.isRequired, - onCloseDialog: PropTypes.func.isRequired, -}; - export default EditMediaDialog; diff --git a/src/components/Dialogs/PreviewMediaDialog/index.jsx b/src/components/Dialogs/PreviewMediaDialog/index.jsx deleted file mode 100644 index 6ac1846db..000000000 --- a/src/components/Dialogs/PreviewMediaDialog/index.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import DialogCloseAnimation from '../../DialogCloseAnimation'; -import PreviewPlayer from '../../Video/Player'; - -function getTitle(media) { - return `${media.artist} – ${media.title}`; -} - -function PreviewDialogWrapper(props) { - return ( -

- ); -} - -const PreviewMediaDialog = ({ - open, - media, - volume, - onCloseDialog, -}) => ( - - {open ? ( - - - {open && ( - - )} - - - ) : null} - -); - -PreviewMediaDialog.propTypes = { - open: PropTypes.bool, - media: PropTypes.object, - volume: PropTypes.number, - onCloseDialog: PropTypes.func.isRequired, -}; - -export default PreviewMediaDialog; diff --git a/src/components/Dialogs/PreviewMediaDialog/index.tsx b/src/components/Dialogs/PreviewMediaDialog/index.tsx new file mode 100644 index 000000000..379ce7b1f --- /dev/null +++ b/src/components/Dialogs/PreviewMediaDialog/index.tsx @@ -0,0 +1,51 @@ +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogCloseAnimation from '../../DialogCloseAnimation'; +import PreviewPlayer from '../../Video/Player'; +import type { Media } from '../../../reducers/booth'; + +function getTitle(media: Media) { + return `${media.artist} – ${media.title}`; +} + +type PreviewMediaDialogProps = { + open: boolean, + media?: Media | null, + volume: number, + onCloseDialog: () => void, +}; +function PreviewMediaDialog({ + open, + media, + volume, + onCloseDialog, +}: PreviewMediaDialogProps) { + const dialog = open && media != null ? ( + + + + + + ) : null; + + return ( + + {dialog} + + ); +} + +export default PreviewMediaDialog; diff --git a/src/components/Player/index.tsx b/src/components/Player/index.tsx index 91f9a0d93..cd856a088 100644 --- a/src/components/Player/index.tsx +++ b/src/components/Player/index.tsx @@ -8,7 +8,7 @@ type PlayerProps = { mode?: 'preview' | undefined, volume: number, isMuted: boolean, - media: Media, + media: Media | null, seek: number, onPlay?: () => void, }; diff --git a/src/components/Video/Player.tsx b/src/components/Video/Player.tsx index c8a0be542..f61b16046 100644 --- a/src/components/Video/Player.tsx +++ b/src/components/Video/Player.tsx @@ -5,13 +5,11 @@ type PreviewPlayerProps = { media: Media, seek?: number, volume: number, - isMuted: boolean, }; function PreviewPlayer({ media, seek = 0, volume, - isMuted, }: PreviewPlayerProps) { const { getMediaSource } = useMediaSources(); const source = getMediaSource(media.sourceType); @@ -27,7 +25,7 @@ function PreviewPlayer({ active seek={seek} media={media} - volume={isMuted ? 0 : volume} + volume={volume} mode="preview" /> ); diff --git a/src/components/Video/VideoProgressBar.jsx b/src/components/Video/VideoProgressBar.jsx deleted file mode 100644 index 3ee9410c8..000000000 --- a/src/components/Video/VideoProgressBar.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import LinearProgress from '@mui/material/LinearProgress'; - -const VideoProgressBar = ({ - media, - seek, -}) => ( -
- -
-); - -VideoProgressBar.propTypes = { - media: PropTypes.shape({ - start: PropTypes.number.isRequired, - end: PropTypes.number.isRequired, - }).isRequired, - seek: PropTypes.number.isRequired, -}; - -export default VideoProgressBar; diff --git a/src/components/Video/VideoProgressBar.tsx b/src/components/Video/VideoProgressBar.tsx new file mode 100644 index 000000000..7de6e5096 --- /dev/null +++ b/src/components/Video/VideoProgressBar.tsx @@ -0,0 +1,20 @@ +import LinearProgress from '@mui/material/LinearProgress'; +import type { Media } from '../../reducers/booth'; + +type VideoProgressBarProps = { + media: Media, + seek: number, +}; +function VideoProgressBar({ media, seek }: VideoProgressBarProps) { + return ( +
+ +
+ ); +} + +export default VideoProgressBar; diff --git a/src/hooks/useRoomHistory.ts b/src/hooks/useRoomHistory.ts index a99ff38f9..a8d6c1a97 100644 --- a/src/hooks/useRoomHistory.ts +++ b/src/hooks/useRoomHistory.ts @@ -10,7 +10,7 @@ interface ApiMedia { _id: string sourceID: string sourceType: string - sourceData: object + sourceData: Record | null artist: string title: string duration: number @@ -30,7 +30,7 @@ interface ApiHistoryEntry { title: string, start: number, end: number, - sourceData: object, + sourceData: Record | null, }, playedAt: string, upvotes: string[], diff --git a/src/mobile/components/App/Overlays.jsx b/src/mobile/components/App/Overlays.tsx similarity index 79% rename from src/mobile/components/App/Overlays.jsx rename to src/mobile/components/App/Overlays.tsx index 48aaa16d3..4b8e9ed0b 100644 --- a/src/mobile/components/App/Overlays.jsx +++ b/src/mobile/components/App/Overlays.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; -const Overlays = ({ children, active }) => { +type OverlaysProps = { + children: React.ReactElement | React.ReactElement[], + active?: string | null, +}; +function Overlays({ children, active }: OverlaysProps) { let view; if (Array.isArray(children)) { view = children.find((child) => child.key === active); @@ -31,11 +33,6 @@ const Overlays = ({ children, active }) => { {view} ); -}; - -Overlays.propTypes = { - children: PropTypes.node, - active: PropTypes.string, -}; +} export default Overlays; diff --git a/src/mobile/components/App/index.jsx b/src/mobile/components/App/index.tsx similarity index 86% rename from src/mobile/components/App/index.jsx rename to src/mobile/components/App/index.tsx index 042e22b68..c577c3b0c 100644 --- a/src/mobile/components/App/index.jsx +++ b/src/mobile/components/App/index.tsx @@ -1,6 +1,5 @@ import cx from 'clsx'; -import React from 'react'; -import PropTypes from 'prop-types'; +import { useState } from 'react'; import Snackbar from '@mui/material/Snackbar'; import { useSelector } from '../../../hooks/useRedux'; import ErrorArea from '../../../containers/ErrorArea'; @@ -15,12 +14,14 @@ import ServerList from '../../containers/ServerList'; import { videoEnabledSelector } from '../../../reducers/settings'; import Overlays from './Overlays'; -const { useState } = React; - +type MobileAppProps = { + activeOverlay?: string | null, + onCloseOverlay: () => void, +}; function MobileApp({ activeOverlay, onCloseOverlay, -}) { +}: MobileAppProps) { const [dismissedWarning, dismissWarning] = useState(false); const videoEnabled = useSelector(videoEnabledSelector); @@ -36,7 +37,7 @@ function MobileApp({ - + @@ -51,9 +52,4 @@ function MobileApp({ ); } -MobileApp.propTypes = { - activeOverlay: PropTypes.string, - onCloseOverlay: PropTypes.func.isRequired, -}; - export default MobileApp; diff --git a/src/mobile/components/MainView/VideoDisabledMessage.jsx b/src/mobile/components/MainView/VideoDisabledMessage.tsx similarity index 68% rename from src/mobile/components/MainView/VideoDisabledMessage.jsx rename to src/mobile/components/MainView/VideoDisabledMessage.tsx index 8e08ae714..4d41514de 100644 --- a/src/mobile/components/MainView/VideoDisabledMessage.jsx +++ b/src/mobile/components/MainView/VideoDisabledMessage.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { useTranslator } from '@u-wave/react-translate'; import Button from '@mui/material/Button'; -function VideoDisabledMessage({ onEnableVideo }) { +type VideoDisabledMessageProps = { + onEnableVideo: () => void, +}; +function VideoDisabledMessage({ onEnableVideo }: VideoDisabledMessageProps) { const { t } = useTranslator(); return ( @@ -16,8 +17,4 @@ function VideoDisabledMessage({ onEnableVideo }) { ); } -VideoDisabledMessage.propTypes = { - onEnableVideo: PropTypes.func.isRequired, -}; - export default VideoDisabledMessage; diff --git a/src/mobile/components/MainView/index.jsx b/src/mobile/components/MainView/index.tsx similarity index 81% rename from src/mobile/components/MainView/index.jsx rename to src/mobile/components/MainView/index.tsx index cd62e00d5..4cd1d7277 100644 --- a/src/mobile/components/MainView/index.jsx +++ b/src/mobile/components/MainView/index.tsx @@ -1,5 +1,3 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { useTranslator } from '@u-wave/react-translate'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; @@ -13,13 +11,14 @@ import Chat from '../../containers/Chat'; import DrawerMenu from '../../containers/DrawerMenu'; import UsersDrawer from '../../containers/UsersDrawer'; import VideoDisabledMessage from './VideoDisabledMessage'; +import type { Media } from '../../../reducers/booth'; -const waitlistIconStyle = { +const waitlistIconStyle: React.CSSProperties = { fontSize: '125%', textAlign: 'center', }; -const getWaitlistLabel = (size, position) => { +function getWaitlistLabel(size: number, position: number) { if (size > 0) { const posText = position !== -1 ? `${position + 1}/${size}` @@ -28,8 +27,18 @@ const getWaitlistLabel = (size, position) => { return posText; } return '0'; -}; +} +type MainViewProps = { + media: Media | null, + videoEnabled: boolean, + waitlistPosition: number, + waitlistSize: number, + onOpenRoomHistory: () => void, + onOpenDrawer: () => void, + onOpenWaitlist: () => void, + onEnableVideo: () => void, +}; function MainView({ media, videoEnabled, @@ -39,7 +48,7 @@ function MainView({ onOpenDrawer, onOpenWaitlist, onEnableVideo, -}) { +}: MainViewProps) { const { t } = useTranslator(); let title = t('booth.empty'); @@ -87,15 +96,4 @@ function MainView({ ); } -MainView.propTypes = { - media: PropTypes.object, - videoEnabled: PropTypes.bool.isRequired, - waitlistPosition: PropTypes.number.isRequired, - waitlistSize: PropTypes.number.isRequired, - onOpenRoomHistory: PropTypes.func.isRequired, - onOpenWaitlist: PropTypes.func.isRequired, - onOpenDrawer: PropTypes.func.isRequired, - onEnableVideo: PropTypes.func.isRequired, -}; - export default MainView; diff --git a/src/mobile/components/MediaList/Row.css b/src/mobile/components/MediaList/Row.css index ceb12c77b..7ff905410 100644 --- a/src/mobile/components/MediaList/Row.css +++ b/src/mobile/components/MediaList/Row.css @@ -1,3 +1,7 @@ +.MobileMediaRow { + position: absolute; +} + .MobileMediaRow-artist, .MobileMediaRow-title { max-width: 100%; diff --git a/src/mobile/components/MediaList/Row.jsx b/src/mobile/components/MediaList/Row.jsx deleted file mode 100644 index bc46db167..000000000 --- a/src/mobile/components/MediaList/Row.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Avatar from '@mui/material/Avatar'; -import ListItem from '@mui/material/ListItem'; -import ListItemAvatar from '@mui/material/ListItemAvatar'; -import ListItemText from '@mui/material/ListItemText'; - -const noWrap = { noWrap: true }; - -const MediaRow = ({ media, style }) => ( - - - - - - -); - -MediaRow.propTypes = { - style: PropTypes.object, // from virtual list positioning - media: PropTypes.object, -}; - -export default MediaRow; diff --git a/src/mobile/components/MediaList/Row.tsx b/src/mobile/components/MediaList/Row.tsx new file mode 100644 index 000000000..57dd158e3 --- /dev/null +++ b/src/mobile/components/MediaList/Row.tsx @@ -0,0 +1,33 @@ +import Avatar from '@mui/material/Avatar'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemText from '@mui/material/ListItemText'; +import type { Media } from '../../../reducers/booth'; + +const noWrap = { noWrap: true }; + +type MediaRowProps = { + media: Media, + /** For virtual list positioning. */ + style: React.CSSProperties, +}; +function MediaRow({ media, style }: MediaRowProps) { + return ( + + + + + + + ); +} + +export default MediaRow; diff --git a/src/mobile/components/MediaList/index.jsx b/src/mobile/components/MediaList/index.tsx similarity index 60% rename from src/mobile/components/MediaList/index.jsx rename to src/mobile/components/MediaList/index.tsx index fd2c96cd5..dfc28dfbb 100644 --- a/src/mobile/components/MediaList/index.jsx +++ b/src/mobile/components/MediaList/index.tsx @@ -1,9 +1,13 @@ -import React from 'react'; import List from '@mui/material/List'; import Base from '../../../components/MediaList/BaseMediaList'; import MediaRow from './Row'; +import type { Media } from '../../../reducers/booth'; -function MediaList(props) { +type MediaListProps = { + className?: string, + media: (Media | null)[], +}; +function MediaList(props: MediaListProps) { return ( ( - - - - - - - -); - -HistoryRow.propTypes = { - style: PropTypes.object, // from virtual list positioning - media: PropTypes.object, -}; - -export default HistoryRow; diff --git a/src/mobile/components/RoomHistory/Row.tsx b/src/mobile/components/RoomHistory/Row.tsx new file mode 100644 index 000000000..84a2bb455 --- /dev/null +++ b/src/mobile/components/RoomHistory/Row.tsx @@ -0,0 +1,35 @@ +import Avatar from '@mui/material/Avatar'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemText from '@mui/material/ListItemText'; +import Votes from './Votes'; +import type { HistoryEntry } from '../../../hooks/useRoomHistory'; + +const noWrap = { noWrap: true }; + +type HistoryRowProps = { + media: HistoryEntry, + /** For virtual list positioning. */ + style: React.CSSProperties, +}; +function HistoryRow({ media, style }: HistoryRowProps) { + return ( + + + + + + + + ); +} + +export default HistoryRow; diff --git a/src/mobile/components/RoomHistory/Votes.jsx b/src/mobile/components/RoomHistory/Votes.tsx similarity index 84% rename from src/mobile/components/RoomHistory/Votes.jsx rename to src/mobile/components/RoomHistory/Votes.tsx index c15d3eed2..a68a4c457 100644 --- a/src/mobile/components/RoomHistory/Votes.jsx +++ b/src/mobile/components/RoomHistory/Votes.tsx @@ -1,12 +1,16 @@ import cx from 'clsx'; -import PropTypes from 'prop-types'; import { mdiHeart, mdiHeartOutline, mdiThumbDown, mdiThumbUp, } from '@mdi/js'; import SvgIcon from '../../../components/SvgIcon'; import useCurrentUser from '../../../hooks/useCurrentUser'; -function Votes({ upvotes, downvotes, favorites }) { +type VotesProps = { + upvotes: string[], + downvotes: string[], + favorites: string[], +}; +function Votes({ upvotes, downvotes, favorites }: VotesProps) { const user = useCurrentUser(); const isUpvote = user ? upvotes.includes(user._id) : false; const isDownvote = user ? downvotes.includes(user._id) : false; @@ -30,10 +34,4 @@ function Votes({ upvotes, downvotes, favorites }) { ); } -Votes.propTypes = { - upvotes: PropTypes.array.isRequired, - favorites: PropTypes.array.isRequired, - downvotes: PropTypes.array.isRequired, -}; - export default Votes; diff --git a/src/mobile/components/RoomHistory/index.jsx b/src/mobile/components/RoomHistory/index.tsx similarity index 70% rename from src/mobile/components/RoomHistory/index.jsx rename to src/mobile/components/RoomHistory/index.tsx index 4b94f65c6..437090350 100644 --- a/src/mobile/components/RoomHistory/index.jsx +++ b/src/mobile/components/RoomHistory/index.tsx @@ -1,16 +1,14 @@ import cx from 'clsx'; -import React from 'react'; -import PropTypes from 'prop-types'; import { useTranslator } from '@u-wave/react-translate'; import OverlayHeader from '../../../components/Overlay/Header'; import OverlayContent from '../../../components/Overlay/Content'; import HistoryList from './HistoryList'; -function RoomHistory({ - className, - onCloseOverlay, - ...props -}) { +type RoomHistoryProps = { + className?: string, + onCloseOverlay: () => void, +}; +function RoomHistory({ className, onCloseOverlay }: RoomHistoryProps) { const { t } = useTranslator(); return ( @@ -22,15 +20,10 @@ function RoomHistory({ onCloseOverlay={onCloseOverlay} /> - + ); } -RoomHistory.propTypes = { - className: PropTypes.string, - onCloseOverlay: PropTypes.func.isRequired, -}; - export default RoomHistory; diff --git a/src/mobile/components/UsersDrawer/UserList.jsx b/src/mobile/components/UsersDrawer/UserList.tsx similarity index 75% rename from src/mobile/components/UsersDrawer/UserList.jsx rename to src/mobile/components/UsersDrawer/UserList.tsx index 88546ca45..3133d376c 100644 --- a/src/mobile/components/UsersDrawer/UserList.jsx +++ b/src/mobile/components/UsersDrawer/UserList.tsx @@ -1,16 +1,15 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { useTranslator } from '@u-wave/react-translate'; import List from '@mui/material/List'; import ListSubheader from '@mui/material/ListSubheader'; import Divider from '@mui/material/Divider'; -import Button from '@mui/material/Button'; +import Button, { type ButtonProps } from '@mui/material/Button'; import { mdiPlay } from '@mdi/js'; import SvgIcon from '../../../components/SvgIcon'; import UserRow from './UserRow'; import WaitlistPosition from './WaitlistPosition'; +import type { User } from '../../../reducers/users'; -function JoinWaitlistButton(props) { +function JoinWaitlistButton(props: ButtonProps) { return (