diff --git a/web/.eslintrc.js b/web/.eslintrc.js index e48bad176..20fc7de32 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -22,7 +22,9 @@ module.exports = { ignorePatterns: ['!./storybook/**'], rules: { 'prettier/prettier': 'error', + 'react/prop-types': 0, 'react/react-in-jsx-scope': 'off', + 'react/require-default-props': 'off', 'react/jsx-filename-extension': [ 1, { @@ -31,7 +33,13 @@ module.exports = { ], 'react/jsx-props-no-spreading': 'off', 'react/jsx-no-bind': 'off', - 'react/function-component-definition': 'off', + 'react/function-component-definition': [ + 'warn', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], '@next/next/no-img-element': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', diff --git a/web/.storybook/stories-category-doc-pages/Chat.stories.mdx b/web/.storybook/stories-category-doc-pages/Chat.stories.mdx index 5e826a1de..877b68de9 100644 --- a/web/.storybook/stories-category-doc-pages/Chat.stories.mdx +++ b/web/.storybook/stories-category-doc-pages/Chat.stories.mdx @@ -1,6 +1,5 @@ import { Meta } from '@storybook/addon-docs'; import { Typography } from 'antd'; -import UserChatMessage from '../../components/chat/ChatUserMessage'; import { ChatMessage } from '../../interfaces/chat-message.model'; diff --git a/web/components/.eslintrc.js b/web/components/.eslintrc.js new file mode 100644 index 000000000..0685a3005 --- /dev/null +++ b/web/components/.eslintrc.js @@ -0,0 +1,22 @@ +// ESLint rules specific to writing react components. + +module.exports = { + rules: { + // Prefer arrow functions when defining functional components + // This enables the `export const Foo: FC = ({ bar }) => ...` style we prefer. + 'react/function-component-definition': [ + 'warn', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], + + // In functional components, mostly ensures Props are defined above components. + '@typescript-eslint/no-use-before-define': 'error', + + // React components tend to use named exports. + // Additionally, the `export default function` syntax cannot be auto-fixed by eslint when using ts. + 'import/prefer-default-export': 'off', + }, +}; diff --git a/web/components/other/ban-user-button/ban-user-button.tsx b/web/components/BanUserButton.tsx similarity index 89% rename from web/components/other/ban-user-button/ban-user-button.tsx rename to web/components/BanUserButton.tsx index 778db4288..de6345c5c 100644 --- a/web/components/other/ban-user-button/ban-user-button.tsx +++ b/web/components/BanUserButton.tsx @@ -1,15 +1,17 @@ import { Modal, Button } from 'antd'; import { ExclamationCircleFilled, QuestionCircleFilled, StopTwoTone } from '@ant-design/icons'; -import { USER_ENABLED_TOGGLE, fetchData } from '../../../utils/apis'; -import { User } from '../../../types/chat'; +import { FC } from 'react'; +import { USER_ENABLED_TOGGLE, fetchData } from '../utils/apis'; +import { User } from '../types/chat'; -interface BanUserButtonProps { +export type BanUserButtonProps = { user: User; isEnabled: Boolean; // = this user's current status label?: string; onClick?: () => void; -} -export default function BanUserButton({ user, isEnabled, label, onClick }: BanUserButtonProps) { +}; + +export const BanUserButton: FC = ({ user, isEnabled, label, onClick }) => { async function buttonClicked({ id }): Promise { const data = { userId: id, @@ -78,7 +80,7 @@ export default function BanUserButton({ user, isEnabled, label, onClick }: BanUs {label || actionString} ); -} +}; BanUserButton.defaultProps = { label: '', onClick: null, diff --git a/web/components/banned-ips-table.tsx b/web/components/BannedIPsTable.tsx similarity index 92% rename from web/components/banned-ips-table.tsx rename to web/components/BannedIPsTable.tsx index ec12d37b4..1b4a7caf4 100644 --- a/web/components/banned-ips-table.tsx +++ b/web/components/BannedIPsTable.tsx @@ -1,7 +1,7 @@ import { Table, Button } from 'antd'; import format from 'date-fns/format'; import { SortOrder } from 'antd/lib/table/interface'; -import React from 'react'; +import React, { FC } from 'react'; import { StopTwoTone } from '@ant-design/icons'; import { User } from '../types/chat'; import { BANNED_IP_REMOVE, fetchData } from '../utils/apis'; @@ -23,7 +23,11 @@ async function removeIPAddressBan(ipAddress: String) { } } -export default function BannedIPsTable({ data }: UserTableProps) { +export type UserTableProps = { + data: User[]; +}; + +export const BannedIPsTable: FC = ({ data }) => { const columns = [ { title: 'IP Address', @@ -68,8 +72,4 @@ export default function BannedIPsTable({ data }: UserTableProps) { rowKey="ipAddress" /> ); -} - -interface UserTableProps { - data: User[]; -} +}; diff --git a/web/components/chart.tsx b/web/components/Chart.tsx similarity index 94% rename from web/components/chart.tsx rename to web/components/Chart.tsx index dc9ce8fc0..bbc87f08a 100644 --- a/web/components/chart.tsx +++ b/web/components/Chart.tsx @@ -2,6 +2,7 @@ import ChartJs from 'chart.js/auto'; import Chartkick from 'chartkick'; import format from 'date-fns/format'; import { LineChart } from 'react-chartkick'; +import { FC } from 'react'; // from https://github.com/ankane/chartkick.js/blob/master/chart.js/chart.esm.js Chartkick.use(ChartJs); @@ -11,7 +12,7 @@ interface TimedValue { value: number; } -interface ChartProps { +export type ChartProps = { data?: TimedValue[]; title?: string; color: string; @@ -19,7 +20,7 @@ interface ChartProps { yFlipped?: boolean; yLogarithmic?: boolean; dataCollections?: any[]; -} +}; function createGraphDataset(dataArray) { const dataValues = {}; @@ -31,7 +32,7 @@ function createGraphDataset(dataArray) { return dataValues; } -export default function Chart({ +export const Chart: FC = ({ data, title, color, @@ -39,7 +40,7 @@ export default function Chart({ dataCollections, yFlipped, yLogarithmic, -}: ChartProps) { +}) => { const renderData = []; if (data && data.length > 0) { @@ -87,7 +88,7 @@ export default function Chart({ /> ); -} +}; Chart.defaultProps = { dataCollections: [], diff --git a/web/components/client-table.tsx b/web/components/ClientTable.tsx similarity index 93% rename from web/components/client-table.tsx rename to web/components/ClientTable.tsx index 90465c5e9..b63949fb5 100644 --- a/web/components/client-table.tsx +++ b/web/components/ClientTable.tsx @@ -3,12 +3,17 @@ import { FilterDropdownProps, SortOrder } from 'antd/lib/table/interface'; import { ColumnsType } from 'antd/es/table'; import { SearchOutlined } from '@ant-design/icons'; import { formatDistanceToNow } from 'date-fns'; +import { FC } from 'react'; import { Client } from '../types/chat'; -import UserPopover from './user-popover'; -import BanUserButton from './other/ban-user-button/ban-user-button'; +import { UserPopover } from './UserPopover'; +import { BanUserButton } from './BanUserButton'; import { formatUAstring } from '../utils/format'; -export default function ClientTable({ data }: ClientTableProps) { +export type ClientTableProps = { + data: Client[]; +}; + +export const ClientTable: FC = ({ data }) => { const columns: ColumnsType = [ { title: 'Display Name', @@ -91,8 +96,4 @@ export default function ClientTable({ data }: ClientTableProps) { rowKey="id" /> ); -} - -interface ClientTableProps { - data: Client[]; -} +}; diff --git a/web/components/Color.tsx b/web/components/Color.tsx index f0aefb1ef..ce1b8fddb 100644 --- a/web/components/Color.tsx +++ b/web/components/Color.tsx @@ -1,7 +1,11 @@ import PropTypes from 'prop-types'; +import { FC } from 'react'; -export function Color(props) { - const { color } = props; +export type ColorProps = { + color: any; // TODO specify better type +}; + +export const Color: FC = ({ color }) => { const resolvedColor = getComputedStyle(document.documentElement).getPropertyValue(`--${color}`); const containerStyle = { @@ -48,7 +52,7 @@ export function Color(props) {
{color}
); -} +}; Color.propTypes = { color: PropTypes.string.isRequired, @@ -61,7 +65,7 @@ const rowStyle = { alignItems: 'center', }; -export function ColorRow(props) { +export const ColorRow = props => { const { colors } = props; return ( @@ -71,7 +75,7 @@ export function ColorRow(props) { ))} ); -} +}; ColorRow.propTypes = { colors: PropTypes.arrayOf(PropTypes.string).isRequired, diff --git a/web/components/compose-federated-post.tsx b/web/components/ComposeFederatedPost.tsx similarity index 90% rename from web/components/compose-federated-post.tsx rename to web/components/ComposeFederatedPost.tsx index e76cdffe1..9e3e18004 100644 --- a/web/components/compose-federated-post.tsx +++ b/web/components/ComposeFederatedPost.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { FC, useState } from 'react'; import { Button, Space, Input, Modal } from 'antd'; import { STATUS_ERROR, STATUS_SUCCESS } from '../utils/input-statuses'; @@ -6,12 +6,12 @@ import { fetchData, FEDERATION_MESSAGE_SEND } from '../utils/apis'; const { TextArea } = Input; -interface ComposeFederatedPostProps { +export type ComposeFederatedPostProps = { visible: boolean; handleClose: () => void; -} +}; -export default function ComposeFederatedPost({ visible, handleClose }: ComposeFederatedPostProps) { +export const ComposeFederatedPost: FC = ({ visible, handleClose }) => { const [content, setContent] = useState(''); const [postPending, setPostPending] = useState(false); const [postSuccessState, setPostSuccessState] = useState(null); @@ -79,4 +79,4 @@ export default function ComposeFederatedPost({ visible, handleClose }: ComposeFe ); -} +}; diff --git a/web/components/ImageAsset.tsx b/web/components/ImageAsset.tsx index 450708f75..1f57381ca 100644 --- a/web/components/ImageAsset.tsx +++ b/web/components/ImageAsset.tsx @@ -1,6 +1,11 @@ -export function ImageAsset(props: ImageAssetProps) { - const { name, src } = props; +import { FC } from 'react'; +export type ImageAssetProps = { + name: string; + src: string; +}; + +export const ImageAsset: FC = ({ name, src }) => { const containerStyle = { borderRadius: '20px', width: '12vw', @@ -38,12 +43,7 @@ export function ImageAsset(props: ImageAssetProps) { ); -} - -interface ImageAssetProps { - name: string; - src: string; -} +}; const rowStyle = { display: 'flex', @@ -53,7 +53,7 @@ const rowStyle = { alignItems: 'center', }; -export function ImageRow(props: ImageRowProps) { +export const ImageRow = (props: ImageRowProps) => { const { images } = props; return ( @@ -63,7 +63,7 @@ export function ImageRow(props: ImageRowProps) { ))} ); -} +}; interface ImageRowProps { images: ImageAssetProps[]; diff --git a/web/components/info-tip.tsx b/web/components/InfoTip.tsx similarity index 72% rename from web/components/info-tip.tsx rename to web/components/InfoTip.tsx index f5b847bb2..7dd12d012 100644 --- a/web/components/info-tip.tsx +++ b/web/components/InfoTip.tsx @@ -1,11 +1,12 @@ import { InfoCircleOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; +import { FC } from 'react'; -interface InfoTipProps { +export type InfoTipProps = { tip: string | null; -} +}; -export default function InfoTip({ tip }: InfoTipProps) { +export const InfoTip: FC = ({ tip }) => { if (tip === '' || tip === null) { return null; } @@ -17,4 +18,4 @@ export default function InfoTip({ tip }: InfoTipProps) { ); -} +}; diff --git a/web/components/key-value-table.tsx b/web/components/KeyValueTable.tsx similarity index 75% rename from web/components/key-value-table.tsx rename to web/components/KeyValueTable.tsx index 91bb05e03..33d7b2208 100644 --- a/web/components/key-value-table.tsx +++ b/web/components/KeyValueTable.tsx @@ -1,8 +1,14 @@ import { Table, Typography } from 'antd'; +import { FC } from 'react'; const { Title } = Typography; -export default function KeyValueTable({ title, data }: KeyValueTableProps) { +export type KeyValueTableProps = { + title: string; + data: any; +}; + +export const KeyValueTable: FC = ({ title, data }) => { const columns = [ { title: 'Name', @@ -22,9 +28,4 @@ export default function KeyValueTable({ title, data }: KeyValueTableProps) { ); -} - -interface KeyValueTableProps { - title: string; - data: any; -} +}; diff --git a/web/components/log-table.tsx b/web/components/LogTable.tsx similarity index 92% rename from web/components/log-table.tsx rename to web/components/LogTable.tsx index 56e12ad5b..5f89d3967 100644 --- a/web/components/log-table.tsx +++ b/web/components/LogTable.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Table, Tag, Typography } from 'antd'; import Linkify from 'react-linkify'; import { SortOrder } from 'antd/lib/table/interface'; @@ -22,12 +22,12 @@ function renderMessage(text) { return {text}; } -interface Props { +export type LogTableProps = { logs: object[]; pageSize: number; -} +}; -export default function LogTable({ logs, pageSize }: Props) { +export const LogTable: FC = ({ logs, pageSize }) => { if (!logs?.length) { return null; } @@ -85,4 +85,4 @@ export default function LogTable({ logs, pageSize }: Props) { /> ); -} +}; diff --git a/web/components/main-layout.tsx b/web/components/MainLayout.tsx similarity index 96% rename from web/components/main-layout.tsx rename to web/components/MainLayout.tsx index 111d8e38a..00a32a36c 100644 --- a/web/components/main-layout.tsx +++ b/web/components/MainLayout.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { FC, ReactNode, useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import Link from 'next/link'; import Head from 'next/head'; @@ -21,19 +21,20 @@ import classNames from 'classnames'; import { upgradeVersionAvailable } from '../utils/apis'; import { parseSecondsToDurationString } from '../utils/format'; -import { OwncastLogo } from './common'; +import { OwncastLogo } from './common/OwncastLogo/OwncastLogo'; import { ServerStatusContext } from '../utils/server-status-context'; import { AlertMessageContext } from '../utils/alert-message-context'; -import TextFieldWithSubmit from './config/form-textfield-with-submit'; +import { TextFieldWithSubmit } from './config/TextFieldWithSubmit'; import { TEXTFIELD_PROPS_STREAM_TITLE } from '../utils/config-constants'; -import ComposeFederatedPost from './compose-federated-post'; +import { ComposeFederatedPost } from './ComposeFederatedPost'; import { UpdateArgs } from '../types/config-section'; -// eslint-disable-next-line react/function-component-definition -export default function MainLayout(props) { - const { children } = props; +export type MainLayoutProps = { + children: ReactNode; +}; +export const MainLayout: FC = ({ children }) => { const context = useContext(ServerStatusContext); const { serverConfig, online, broadcaster, versionNumber } = context || {}; const { instanceDetails, chatDisabled, federation } = serverConfig; @@ -287,7 +288,7 @@ export default function MainLayout(props) { /> ); -} +}; MainLayout.propTypes = { children: PropTypes.element.isRequired, diff --git a/web/components/message-visiblity-toggle.tsx b/web/components/MessageVisiblityToggle.tsx similarity index 93% rename from web/components/message-visiblity-toggle.tsx rename to web/components/MessageVisiblityToggle.tsx index f61323a66..9d9a5181e 100644 --- a/web/components/message-visiblity-toggle.tsx +++ b/web/components/MessageVisiblityToggle.tsx @@ -1,5 +1,5 @@ // Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, FC } from 'react'; import { Button, Tooltip } from 'antd'; import { EyeOutlined, @@ -12,17 +12,17 @@ import { MessageType } from '../types/chat'; import { OUTCOME_TIMEOUT } from '../pages/admin/chat/messages'; import { isEmptyObject } from '../utils/format'; -interface MessageToggleProps { +export type MessageToggleProps = { isVisible: boolean; message: MessageType; setMessage: (message: MessageType) => void; -} +}; -export default function MessageVisiblityToggle({ +export const MessageVisiblityToggle: FC = ({ isVisible, message, setMessage, -}: MessageToggleProps) { +}) => { if (!message || isEmptyObject(message)) { return null; } @@ -89,4 +89,4 @@ export default function MessageVisiblityToggle({ ); -} +}; diff --git a/web/components/moderator-user-button.tsx b/web/components/ModeratorUserButton.tsx similarity index 93% rename from web/components/moderator-user-button.tsx rename to web/components/ModeratorUserButton.tsx index b92ffa0d5..4304b5ffc 100644 --- a/web/components/moderator-user-button.tsx +++ b/web/components/ModeratorUserButton.tsx @@ -5,14 +5,16 @@ import { StopTwoTone, SafetyCertificateTwoTone, } from '@ant-design/icons'; +import { FC } from 'react'; import { USER_SET_MODERATOR, fetchData } from '../utils/apis'; import { User } from '../types/chat'; -interface ModeratorUserButtonProps { +export type ModeratorUserButtonProps = { user: User; onClick?: () => void; -} -export default function ModeratorUserButton({ user, onClick }: ModeratorUserButtonProps) { +}; + +export const ModeratorUserButton: FC = ({ user, onClick }) => { async function buttonClicked({ id }, setAsModerator: Boolean): Promise { const data = { userId: id, @@ -87,7 +89,8 @@ export default function ModeratorUserButton({ user, onClick }: ModeratorUserButt {actionString} ); -} +}; + ModeratorUserButton.defaultProps = { onClick: null, }; diff --git a/web/components/news-feed.tsx b/web/components/NewsFeed.tsx similarity index 85% rename from web/components/news-feed.tsx rename to web/components/NewsFeed.tsx index 7063b08e1..e47d24c34 100644 --- a/web/components/news-feed.tsx +++ b/web/components/NewsFeed.tsx @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ /* eslint-disable react/no-danger */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, FC } from 'react'; import { Collapse, Typography, Skeleton } from 'antd'; import format from 'date-fns/format'; @@ -12,14 +12,19 @@ const { Title, Link } = Typography; const OWNCAST_FEED_URL = 'https://owncast.online/news/index.json'; const OWNCAST_BASE_URL = 'https://owncast.online'; -interface Article { +export type ArticleProps = { title: string; url: string; content_html: string; date_published: string; -} +}; -function ArticleItem({ title, url, content_html: content, date_published: date }: Article) { +const ArticleItem: FC = ({ + title, + url, + content_html: content, + date_published: date, +}) => { const dateObject = new Date(date); const dateString = format(dateObject, 'MMM dd, yyyy, HH:mm'); return ( @@ -38,10 +43,10 @@ function ArticleItem({ title, url, content_html: content, date_published: date } ); -} +}; -export default function NewsFeed() { - const [feed, setFeed] = useState([]); +export const NewsFeed = () => { + const [feed, setFeed] = useState([]); const [loading, setLoading] = useState(true); const getFeed = async () => { @@ -75,4 +80,4 @@ export default function NewsFeed() { {noNews} ); -} +}; diff --git a/web/components/offline-notice.tsx b/web/components/Offline.tsx similarity index 93% rename from web/components/offline-notice.tsx rename to web/components/Offline.tsx index 0d9d7c739..4c4ebadf0 100644 --- a/web/components/offline-notice.tsx +++ b/web/components/Offline.tsx @@ -1,10 +1,10 @@ import { BookTwoTone, MessageTwoTone, PlaySquareTwoTone, ProfileTwoTone } from '@ant-design/icons'; import { Card, Col, Row, Typography } from 'antd'; import Link from 'next/link'; -import { useContext } from 'react'; -import LogTable from './log-table'; -import OwncastLogo from './common/Logo/Logo'; -import NewsFeed from './news-feed'; +import { FC, useContext } from 'react'; +import { LogTable } from './LogTable'; +import { OwncastLogo } from './common/OwncastLogo/OwncastLogo'; +import { NewsFeed } from './NewsFeed'; import { ConfigDetails } from '../types/config-section'; import { ServerStatusContext } from '../utils/server-status-context'; @@ -17,12 +17,12 @@ function generateStreamURL(serverURL, rtmpServerPort) { return `rtmp://${serverURL.replace(/(^\w+:|^)\/\//, '')}:${rtmpServerPort}/live`; } -type OfflineProps = { +export type OfflineProps = { logs: any[]; config: ConfigDetails; }; -export default function Offline({ logs = [], config }: OfflineProps) { +export const Offline: FC = ({ logs = [], config }) => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig } = serverStatusData || {}; @@ -149,4 +149,5 @@ export default function Offline({ logs = [], config }: OfflineProps) { ); -} +}; +export default Offline; diff --git a/web/components/statistic.tsx b/web/components/StatisticItem.tsx similarity index 68% rename from web/components/statistic.tsx rename to web/components/StatisticItem.tsx index b12a1deba..b638216ba 100644 --- a/web/components/statistic.tsx +++ b/web/components/StatisticItem.tsx @@ -3,10 +3,11 @@ // TODO: This component should be cleaned up and usage should be re-examined. The types should be reconsidered as well. import { Typography, Statistic, Card, Progress } from 'antd'; +import { FC } from 'react'; const { Text } = Typography; -interface StatisticItemProps { +export type StatisticItemProps = { title?: string; value?: any; prefix?: any; @@ -15,7 +16,8 @@ interface StatisticItemProps { progress?: boolean; centered?: boolean; formatter?: any; -} +}; + const defaultProps = { title: '', value: 0, @@ -27,31 +29,29 @@ const defaultProps = { formatter: null, }; -interface ContentProps { +export type ContentProps = { prefix: string; value: any; suffix: string; title: string; -} +}; -function Content({ prefix, value, suffix, title }: ContentProps) { - return ( +const Content: FC = ({ prefix, value, suffix, title }) => ( +
+ {prefix}
- {prefix} -
- {title} -
-
- - {value} - {suffix || '%'} - -
+ {title}
- ); -} +
+ + {value} + {suffix || '%'} + +
+
+); -function ProgressView({ title, value, prefix, suffix, color }: StatisticItemProps) { +const ProgressView: FC = ({ title, value, prefix, suffix, color }) => { const endColor = value > 90 ? 'red' : color; const content = ; @@ -67,15 +67,15 @@ function ProgressView({ title, value, prefix, suffix, color }: StatisticItemProp format={() => content} /> ); -} +}; ProgressView.defaultProps = defaultProps; -function StatisticView({ title, value, prefix, formatter }: StatisticItemProps) { - return ; -} +const StatisticView: FC = ({ title, value, prefix, formatter }) => ( + +); StatisticView.defaultProps = defaultProps; -export default function StatisticItem(props: StatisticItemProps) { +export const StatisticItem: FC = props => { const { progress, centered } = props; const View = progress ? ProgressView : StatisticView; @@ -88,5 +88,5 @@ export default function StatisticItem(props: StatisticItemProps) { ); -} +}; StatisticItem.defaultProps = defaultProps; diff --git a/web/components/stream-health-overview.tsx b/web/components/StreamHealthOverview.tsx similarity index 92% rename from web/components/stream-health-overview.tsx rename to web/components/StreamHealthOverview.tsx index 2393b533c..815a0a608 100644 --- a/web/components/stream-health-overview.tsx +++ b/web/components/StreamHealthOverview.tsx @@ -1,15 +1,14 @@ import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { Alert, Button, Col, Row, Statistic, Typography } from 'antd'; import Link from 'next/link'; -import React, { useContext } from 'react'; +import React, { FC, useContext } from 'react'; import { ServerStatusContext } from '../utils/server-status-context'; -interface StreamHealthOverviewProps { +export type StreamHealthOverviewProps = { showTroubleshootButton?: Boolean; -} -export default function StreamHealthOverview({ - showTroubleshootButton, -}: StreamHealthOverviewProps) { +}; + +export const StreamHealthOverview: FC = ({ showTroubleshootButton }) => { const serverStatusData = useContext(ServerStatusContext); const { health } = serverStatusData; if (!health) { @@ -79,7 +78,7 @@ export default function StreamHealthOverview({ ); -} +}; StreamHealthOverview.defaultProps = { showTroubleshootButton: true, diff --git a/web/components/user-popover.tsx b/web/components/UserPopover.tsx similarity index 90% rename from web/components/user-popover.tsx rename to web/components/UserPopover.tsx index f0afe436e..ab4420aa2 100644 --- a/web/components/user-popover.tsx +++ b/web/components/UserPopover.tsx @@ -1,25 +1,25 @@ // This displays a clickable user name (or whatever children element you provide), and displays a simple tooltip of created time. OnClick a modal with more information about the user is displayed. -import { useState, ReactNode } from 'react'; +import { useState, ReactNode, FC } from 'react'; import { Divider, Modal, Tooltip, Typography, Row, Col, Space } from 'antd'; import formatDistanceToNow from 'date-fns/formatDistanceToNow'; import format from 'date-fns/format'; import { uniq } from 'lodash'; -import BlockUserbutton from './other/ban-user-button/ban-user-button'; -import ModeratorUserbutton from './moderator-user-button'; +import { BanUserButton } from './BanUserButton'; +import { ModeratorUserButton } from './ModeratorUserButton'; import { User, UserConnectionInfo } from '../types/chat'; -import { formatDisplayDate } from './user-table'; +import { formatDisplayDate } from './UserTable'; import { formatUAstring } from '../utils/format'; -interface UserPopoverProps { +export type UserPopoverProps = { user: User; connectionInfo?: UserConnectionInfo | null; children: ReactNode; -} +}; -export default function UserPopover({ user, connectionInfo, children }: UserPopoverProps) { +export const UserPopover: FC = ({ user, connectionInfo, children }) => { const [isModalVisible, setIsModalVisible] = useState(false); const handleShowModal = () => { setIsModalVisible(true); @@ -123,7 +123,7 @@ export default function UserPopover({ user, connectionInfo, children }: UserPopo This user was banned on {formatDisplayDate(disabledAt)}.

- ) : ( - )} - + ); -} +}; UserPopover.defaultProps = { connectionInfo: null, diff --git a/web/components/user-table.tsx b/web/components/UserTable.tsx similarity index 88% rename from web/components/user-table.tsx rename to web/components/UserTable.tsx index 723fcd92e..a91ec724c 100644 --- a/web/components/user-table.tsx +++ b/web/components/UserTable.tsx @@ -1,14 +1,20 @@ import { Table } from 'antd'; import format from 'date-fns/format'; import { SortOrder } from 'antd/lib/table/interface'; +import { FC } from 'react'; import { User } from '../types/chat'; -import UserPopover from './user-popover'; -import BanUserButton from './other/ban-user-button/ban-user-button'; +import { UserPopover } from './UserPopover'; +import { BanUserButton } from './BanUserButton'; export function formatDisplayDate(date: string | Date) { return format(new Date(date), 'MMM d H:mma'); } -export default function UserTable({ data }: UserTableProps) { + +export type UserTableProps = { + data: User[]; +}; + +export const UserTable: FC = ({ data }) => { const columns = [ { title: 'Last Known Display Name', @@ -57,8 +63,4 @@ export default function UserTable({ data }: UserTableProps) { rowKey="id" /> ); -} - -interface UserTableProps { - data: User[]; -} +}; diff --git a/web/components/viewer-table.tsx b/web/components/ViewerTable.tsx similarity index 90% rename from web/components/viewer-table.tsx rename to web/components/ViewerTable.tsx index 7886322b7..6f9ffafcd 100644 --- a/web/components/viewer-table.tsx +++ b/web/components/ViewerTable.tsx @@ -2,13 +2,19 @@ import { Table } from 'antd'; import format from 'date-fns/format'; import { SortOrder } from 'antd/lib/table/interface'; import { formatDistanceToNow } from 'date-fns'; +import { FC } from 'react'; import { User } from '../types/chat'; import { formatUAstring } from '../utils/format'; export function formatDisplayDate(date: string | Date) { return format(new Date(date), 'MMM d H:mma'); } -export default function ViewerTable({ data }: ViewerTableProps) { + +export type ViewerTableProps = { + data: User[]; +}; + +export const ViewerTable: FC = ({ data }) => { const columns = [ { title: 'User Agent', @@ -43,8 +49,4 @@ export default function ViewerTable({ data }: ViewerTableProps) { rowKey="id" /> ); -} - -interface ViewerTableProps { - data: User[]; -} +}; diff --git a/web/components/action-buttons/ActionButton/ActionButton.stories.tsx b/web/components/action-buttons/ActionButton/ActionButton.stories.tsx index d105b94fa..d98fa7742 100644 --- a/web/components/action-buttons/ActionButton/ActionButton.stories.tsx +++ b/web/components/action-buttons/ActionButton/ActionButton.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ActionButton from './ActionButton'; +import { ActionButton } from './ActionButton'; export default { title: 'owncast/Components/Action Buttons/Single button', diff --git a/web/components/action-buttons/ActionButton/ActionButton.tsx b/web/components/action-buttons/ActionButton/ActionButton.tsx index 12bfa122b..658cdfaf2 100644 --- a/web/components/action-buttons/ActionButton/ActionButton.tsx +++ b/web/components/action-buttons/ActionButton/ActionButton.tsx @@ -1,24 +1,21 @@ import { Button } from 'antd'; -import { useState } from 'react'; -import Modal from '../../ui/Modal/Modal'; +import { FC, useState } from 'react'; +import { Modal } from '../../ui/Modal/Modal'; import { ExternalAction } from '../../../interfaces/external-action'; -import s from './ActionButton.module.scss'; +import styles from './ActionButton.module.scss'; -interface Props { +export type ActionButtonProps = { action: ExternalAction; primary?: boolean; -} -ActionButton.defaultProps = { - primary: true, }; -export default function ActionButton({ +export const ActionButton: FC = ({ action: { url, title, description, icon, color, openExternally }, - primary = false, -}: Props) { + primary = true, +}) => { const [showModal, setShowModal] = useState(false); - const buttonClicked = () => { + const onButtonClicked = () => { if (openExternally) { window.open(url, '_blank'); } else { @@ -30,11 +27,11 @@ export default function ActionButton({ <> ); -} +}; diff --git a/web/components/action-buttons/ActionButtonRow/ActionButtonRow.stories.tsx b/web/components/action-buttons/ActionButtonRow/ActionButtonRow.stories.tsx index dd0d408fd..ae7550fad 100644 --- a/web/components/action-buttons/ActionButtonRow/ActionButtonRow.stories.tsx +++ b/web/components/action-buttons/ActionButtonRow/ActionButtonRow.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ActionButtonRow from './ActionButtonRow'; -import ActionButton from '../ActionButton/ActionButton'; +import { ActionButtonRow } from './ActionButtonRow'; +import { ActionButton } from '../ActionButton/ActionButton'; export default { title: 'owncast/Components/Action Buttons/Buttons Row', diff --git a/web/components/action-buttons/ActionButtonRow/ActionButtonRow.tsx b/web/components/action-buttons/ActionButtonRow/ActionButtonRow.tsx index b9ca3383e..2a39fcfbf 100644 --- a/web/components/action-buttons/ActionButtonRow/ActionButtonRow.tsx +++ b/web/components/action-buttons/ActionButtonRow/ActionButtonRow.tsx @@ -1,12 +1,10 @@ -import React from 'react'; -import s from './ActionButtonRow.module.scss'; +import { FC, ReactNode } from 'react'; +import styles from './ActionButtonRow.module.scss'; -interface Props { - children: React.ReactNode[]; -} +export type ActionButtonRowProps = { + children: ReactNode; +}; -export default function ActionButtonRow(props: Props) { - const { children } = props; - - return
{children}
; -} +export const ActionButtonRow: FC = ({ children }) => ( +
{children}
+); diff --git a/web/components/action-buttons/FollowButton.tsx b/web/components/action-buttons/FollowButton.tsx index a73047d32..0b8de983b 100644 --- a/web/components/action-buttons/FollowButton.tsx +++ b/web/components/action-buttons/FollowButton.tsx @@ -1,31 +1,29 @@ -import { Button } from 'antd'; +import { Button, ButtonProps } from 'antd'; import { HeartFilled } from '@ant-design/icons'; -import { useState } from 'react'; +import { FC, useState } from 'react'; import { useRecoilValue } from 'recoil'; -import Modal from '../ui/Modal/Modal'; -import FollowModal from '../modals/FollowModal/FollowModal'; -import s from './ActionButton/ActionButton.module.scss'; +import { Modal } from '../ui/Modal/Modal'; +import { FollowModal } from '../modals/FollowModal/FollowModal'; +import styles from './ActionButton/ActionButton.module.scss'; import { clientConfigStateAtom } from '../stores/ClientConfigStore'; import { ClientConfig } from '../../interfaces/client-config.model'; -export default function FollowButton(props: any) { +export type FollowButtonProps = ButtonProps; + +export const FollowButton: FC = props => { const [showModal, setShowModal] = useState(false); const clientConfig = useRecoilValue(clientConfigStateAtom); const { name, federation } = clientConfig; const { account } = federation; - const buttonClicked = () => { - setShowModal(true); - }; - return ( <> @@ -40,4 +38,4 @@ export default function FollowButton(props: any) {
); -} +}; diff --git a/web/components/action-buttons/NotifyButton.tsx b/web/components/action-buttons/NotifyButton.tsx index da80e98a6..e292cba29 100644 --- a/web/components/action-buttons/NotifyButton.tsx +++ b/web/components/action-buttons/NotifyButton.tsx @@ -1,15 +1,14 @@ import { Button } from 'antd'; import { BellFilled } from '@ant-design/icons'; -import s from './ActionButton/ActionButton.module.scss'; +import { FC } from 'react'; +import styles from './ActionButton/ActionButton.module.scss'; -interface Props { - onClick: () => void; -} +export type NotifyButtonProps = { + onClick?: () => void; +}; -export default function NotifyButton({ onClick }: Props) { - return ( - - ); -} +export const NotifyButton: FC = ({ onClick }) => ( + +); diff --git a/web/components/chat/ChatActionMessage/ChatActionMessage.stories.tsx b/web/components/chat/ChatActionMessage/ChatActionMessage.stories.tsx index b97d2358d..74c8c71b1 100644 --- a/web/components/chat/ChatActionMessage/ChatActionMessage.stories.tsx +++ b/web/components/chat/ChatActionMessage/ChatActionMessage.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatActionMessage from './ChatActionMessage'; +import { ChatActionMessage } from './ChatActionMessage'; import Mock from '../../../stories/assets/mocks/chatmessage-action.png'; export default { diff --git a/web/components/chat/ChatActionMessage/ChatActionMessage.tsx b/web/components/chat/ChatActionMessage/ChatActionMessage.tsx index 3ad56c248..6ef38cd7a 100644 --- a/web/components/chat/ChatActionMessage/ChatActionMessage.tsx +++ b/web/components/chat/ChatActionMessage/ChatActionMessage.tsx @@ -1,13 +1,12 @@ -import s from './ChatActionMessage.module.scss'; +import { FC } from 'react'; +import styles from './ChatActionMessage.module.scss'; /* eslint-disable react/no-danger */ -interface Props { +export type ChatActionMessageProps = { body: string; -} +}; // eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function ChatActionMessage(props: Props) { - const { body } = props; - - return
; -} +export const ChatActionMessage: FC = ({ body }) => ( +
+); diff --git a/web/components/chat/ChatContainer/ChatContainer.stories.tsx b/web/components/chat/ChatContainer/ChatContainer.stories.tsx index df183788b..d8f5026bc 100644 --- a/web/components/chat/ChatContainer/ChatContainer.stories.tsx +++ b/web/components/chat/ChatContainer/ChatContainer.stories.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import ChatContainer from './index'; +import { ChatContainer } from './ChatContainer'; import { ChatMessage } from '../../../interfaces/chat-message.model'; export default { diff --git a/web/components/chat/ChatContainer/ChatContainer.tsx b/web/components/chat/ChatContainer/ChatContainer.tsx index 620194206..8186d7f3d 100644 --- a/web/components/chat/ChatContainer/ChatContainer.tsx +++ b/web/components/chat/ChatContainer/ChatContainer.tsx @@ -1,32 +1,77 @@ import { Button } from 'antd'; import { Virtuoso } from 'react-virtuoso'; -import { useState, useMemo, useRef, CSSProperties } from 'react'; +import { useState, useMemo, useRef, CSSProperties, FC } from 'react'; import { EditFilled, VerticalAlignBottomOutlined } from '@ant-design/icons'; import { ConnectedClientInfoEvent, MessageType, NameChangeEvent, } from '../../../interfaces/socket-events'; -import s from './ChatContainer.module.scss'; +import styles from './ChatContainer.module.scss'; import { ChatMessage } from '../../../interfaces/chat-message.model'; -import { ChatTextField, ChatUserMessage } from '..'; -import ChatModeratorNotification from '../ChatModeratorNotification/ChatModeratorNotification'; +import { ChatUserMessage } from '../ChatUserMessage/ChatUserMessage'; +import { ChatTextField } from '../ChatTextField/ChatTextField'; +import { ChatModeratorNotification } from '../ChatModeratorNotification/ChatModeratorNotification'; // import ChatActionMessage from '../ChatAction/ChatActionMessage'; -import ChatSystemMessage from '../ChatSystemMessage/ChatSystemMessage'; -import ChatJoinMessage from '../ChatJoinMessage/ChatJoinMessage'; +import { ChatSystemMessage } from '../ChatSystemMessage/ChatSystemMessage'; +import { ChatJoinMessage } from '../ChatJoinMessage/ChatJoinMessage'; -interface Props { +export type ChatContainerProps = { messages: ChatMessage[]; usernameToHighlight: string; chatUserId: string; isModerator: boolean; showInput?: boolean; height?: string; +}; + +function shouldCollapseMessages(messages: ChatMessage[], index: number): boolean { + if (messages.length < 2) { + return false; + } + + const message = messages[index]; + const { + user: { id }, + } = message; + const lastMessage = messages[index - 1]; + if (lastMessage?.type !== MessageType.CHAT) { + return false; + } + + if (!lastMessage.timestamp || !message.timestamp) { + return false; + } + + const maxTimestampDelta = 1000 * 60 * 2; // 2 minutes + const lastTimestamp = new Date(lastMessage.timestamp).getTime(); + const thisTimestamp = new Date(message.timestamp).getTime(); + if (thisTimestamp - lastTimestamp > maxTimestampDelta) { + return false; + } + + return id === lastMessage?.user.id; } -export default function ChatContainer(props: Props) { - const { messages, usernameToHighlight, chatUserId, isModerator, showInput, height } = props; +function checkIsModerator(message) { + const { user } = message; + const { scopes } = user; + if (!scopes || scopes.length === 0) { + return false; + } + + return scopes.includes('MODERATOR'); +} + +export const ChatContainer: FC = ({ + messages, + usernameToHighlight, + chatUserId, + isModerator, + showInput, + height, +}) => { const [atBottom, setAtBottom] = useState(false); // const [showButton, setShowButton] = useState(false); const chatContainerRef = useRef(null); @@ -38,13 +83,13 @@ export default function ChatContainer(props: Props) { const color = `var(--theme-color-users-${displayColor})`; return ( -
+
-
+
{oldName} - is now known as + is now known as {displayName}
@@ -129,7 +174,7 @@ export default function ChatContainer(props: Props) { atBottomStateChange={bottom => setAtBottom(bottom)} /> {!atBottom && ( -
+
); -} - -function shouldCollapseMessages(messages: ChatMessage[], index: number): boolean { - if (messages.length < 2) { - return false; - } - - const message = messages[index]; - const { - user: { id }, - } = message; - const lastMessage = messages[index - 1]; - if (lastMessage?.type !== MessageType.CHAT) { - return false; - } - - if (!lastMessage.timestamp || !message.timestamp) { - return false; - } - - const maxTimestampDelta = 1000 * 60 * 2; // 2 minutes - const lastTimestamp = new Date(lastMessage.timestamp).getTime(); - const thisTimestamp = new Date(message.timestamp).getTime(); - if (thisTimestamp - lastTimestamp > maxTimestampDelta) { - return false; - } - - return id === lastMessage?.user.id; -} - -function checkIsModerator(message) { - const { user } = message; - const { scopes } = user; - - if (!scopes || scopes.length === 0) { - return false; - } - - return scopes.includes('MODERATOR'); -} +}; ChatContainer.defaultProps = { showInput: true, diff --git a/web/components/chat/ChatContainer/index.ts b/web/components/chat/ChatContainer/index.ts deleted file mode 100644 index de0f0f948..000000000 --- a/web/components/chat/ChatContainer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ChatContainer'; diff --git a/web/components/chat/ChatJoinMessage/ChatJoinMessage.stories.tsx b/web/components/chat/ChatJoinMessage/ChatJoinMessage.stories.tsx index e628b0ece..474931fb0 100644 --- a/web/components/chat/ChatJoinMessage/ChatJoinMessage.stories.tsx +++ b/web/components/chat/ChatJoinMessage/ChatJoinMessage.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatJoinMessage from './ChatJoinMessage'; +import { ChatJoinMessage } from './ChatJoinMessage'; import Mock from '../../../stories/assets/mocks/chatmessage-action.png'; export default { diff --git a/web/components/chat/ChatJoinMessage/ChatJoinMessage.tsx b/web/components/chat/ChatJoinMessage/ChatJoinMessage.tsx index 894024b50..466e22806 100644 --- a/web/components/chat/ChatJoinMessage/ChatJoinMessage.tsx +++ b/web/components/chat/ChatJoinMessage/ChatJoinMessage.tsx @@ -1,18 +1,22 @@ -import s from './ChatJoinMessage.module.scss'; -import ChatUserBadge from '../ChatUserBadge/ChatUserBadge'; +import { FC } from 'react'; +import styles from './ChatJoinMessage.module.scss'; +import { ChatUserBadge } from '../ChatUserBadge/ChatUserBadge'; -interface Props { +export type ChatJoinMessageProps = { isAuthorModerator: boolean; userColor: number; displayName: string; -} +}; -export default function ChatJoinMessage(props: Props) { - const { isAuthorModerator, userColor, displayName } = props; +export const ChatJoinMessage: FC = ({ + isAuthorModerator, + userColor, + displayName, +}) => { const color = `var(--theme-user-colors-${userColor})`; return ( -
+
{displayName} {isAuthorModerator && ( @@ -24,4 +28,4 @@ export default function ChatJoinMessage(props: Props) { joined the chat.
); -} +}; diff --git a/web/components/chat/ChatModerationActionMenu/ChatModerationActionMenu.stories.tsx b/web/components/chat/ChatModerationActionMenu/ChatModerationActionMenu.stories.tsx index f2bcb0f2d..80a24162a 100644 --- a/web/components/chat/ChatModerationActionMenu/ChatModerationActionMenu.stories.tsx +++ b/web/components/chat/ChatModerationActionMenu/ChatModerationActionMenu.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import ChatModerationActionMenu from './ChatModerationActionMenu'; +import { ChatModerationActionMenu } from './ChatModerationActionMenu'; const mocks = { mocks: [ @@ -82,7 +82,7 @@ export default { } as ComponentMeta; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const Template: ComponentStory = args => ( +const Template: ComponentStory = () => ( = ({ + messageID, + userID, + userDisplayName, + accessToken, +}) => { const [showUserDetailsModal, setShowUserDetailsModal] = useState(false); const handleBanUser = async () => { @@ -78,7 +82,7 @@ export default function ChatModerationActionMenu(props: Props) { { label: (
- + Hide Message @@ -89,7 +93,7 @@ export default function ChatModerationActionMenu(props: Props) { { label: (
- + Ban User @@ -127,4 +131,4 @@ export default function ChatModerationActionMenu(props: Props) { ); -} +}; diff --git a/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.stories.tsx b/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.stories.tsx index 92badbb3a..85d102d84 100644 --- a/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.stories.tsx +++ b/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import ChatModerationDetailsModal from './ChatModerationDetailsModal'; +import { ChatModerationDetailsModal } from './ChatModerationDetailsModal'; const mocks = { mocks: [ @@ -82,7 +82,7 @@ export default { } as ComponentMeta; // eslint-disable-next-line @typescript-eslint/no-unused-vars -const Template: ComponentStory = args => ( +const Template: ComponentStory = () => ( diff --git a/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.tsx b/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.tsx index 234533183..9d6a9ef9f 100644 --- a/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.tsx +++ b/web/components/chat/ChatModerationDetailsModal/ChatModerationDetailsModal.tsx @@ -1,12 +1,12 @@ import { Button, Col, Row, Spin } from 'antd'; -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import ChatModeration from '../../../services/moderation-service'; -import s from './ChatModerationDetailsModal.module.scss'; +import styles from './ChatModerationDetailsModal.module.scss'; -interface Props { +export type ChatModerationDetailsModalProps = { userId: string; accessToken: string; -} +}; export interface UserDetails { user: User; @@ -91,7 +91,7 @@ const UserColorBlock = ({ color }) => {
Color-
+
{color}
@@ -99,8 +99,10 @@ const UserColorBlock = ({ color }) => { ); }; -export default function ChatModerationDetailsModal(props: Props) { - const { userId, accessToken } = props; +export const ChatModerationDetailsModal: FC = ({ + userId, + accessToken, +}) => { const [userDetails, setUserDetails] = useState(null); const [loading, setLoading] = useState(true); @@ -127,7 +129,7 @@ export default function ChatModerationDetailsModal(props: Props) { user; return ( -
+

{displayName}

@@ -161,7 +163,7 @@ export default function ChatModerationDetailsModal(props: Props) {

Recent Chat Messages

-
+
{messages.map(message => (
); -} +}; diff --git a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.stories.tsx b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.stories.tsx index 8edd83fd3..297f93c61 100644 --- a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.stories.tsx +++ b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatModeratorNotification from './ChatModeratorNotification'; +import { ChatModeratorNotification } from './ChatModeratorNotification'; export default { title: 'owncast/Chat/Messages/Moderation Role Notification', diff --git a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx index 224e33e78..7d83e2978 100644 --- a/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx +++ b/web/components/chat/ChatModeratorNotification/ChatModeratorNotification.tsx @@ -1,12 +1,9 @@ -import s from './ChatModeratorNotification.module.scss'; +import styles from './ChatModeratorNotification.module.scss'; import Icon from '../../../assets/images/moderator.svg'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function ModeratorNotification() { - return ( -
- - You are now a moderator. -
- ); -} +export const ChatModeratorNotification = () => ( +
+ + You are now a moderator. +
+); diff --git a/web/components/chat/ChatSocialMessage/ChatSocialMessage.stories.tsx b/web/components/chat/ChatSocialMessage/ChatSocialMessage.stories.tsx index b47b913d7..be8c07c04 100644 --- a/web/components/chat/ChatSocialMessage/ChatSocialMessage.stories.tsx +++ b/web/components/chat/ChatSocialMessage/ChatSocialMessage.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatSocialMessage from './ChatSocialMessage'; +import { ChatSocialMessage } from './ChatSocialMessage'; export default { title: 'owncast/Chat/Messages/Social-fediverse event', diff --git a/web/components/chat/ChatSocialMessage/ChatSocialMessage.tsx b/web/components/chat/ChatSocialMessage/ChatSocialMessage.tsx index 3b456c5ea..9e94fe9d2 100644 --- a/web/components/chat/ChatSocialMessage/ChatSocialMessage.tsx +++ b/web/components/chat/ChatSocialMessage/ChatSocialMessage.tsx @@ -1,11 +1,11 @@ /* eslint-disable react/no-unused-prop-types */ /* eslint-disable @typescript-eslint/no-unused-vars */ +// TODO remove unused props +import { FC } from 'react'; import { ChatMessage } from '../../../interfaces/chat-message.model'; -interface Props { +export interface ChatSocialMessageProps { message: ChatMessage; } -export default function ChatSocialMessage(props: Props) { - return
Component goes here
; -} +export const ChatSocialMessage: FC = () =>
Component goes here
; diff --git a/web/components/chat/ChatSystemMessage/ChatSystemMessage.stories.tsx b/web/components/chat/ChatSystemMessage/ChatSystemMessage.stories.tsx index 78f435fea..b0c207ace 100644 --- a/web/components/chat/ChatSystemMessage/ChatSystemMessage.stories.tsx +++ b/web/components/chat/ChatSystemMessage/ChatSystemMessage.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatSystemMessage from './ChatSystemMessage'; +import { ChatSystemMessage } from './ChatSystemMessage'; import Mock from '../../../stories/assets/mocks/chatmessage-system.png'; import { ChatMessage } from '../../../interfaces/chat-message.model'; diff --git a/web/components/chat/ChatSystemMessage/ChatSystemMessage.tsx b/web/components/chat/ChatSystemMessage/ChatSystemMessage.tsx index 042a332b9..e8eb1d61a 100644 --- a/web/components/chat/ChatSystemMessage/ChatSystemMessage.tsx +++ b/web/components/chat/ChatSystemMessage/ChatSystemMessage.tsx @@ -1,25 +1,27 @@ /* eslint-disable react/no-danger */ import { Highlight } from 'react-highlighter-ts'; +import { FC } from 'react'; import { ChatMessage } from '../../../interfaces/chat-message.model'; -import s from './ChatSystemMessage.module.scss'; +import styles from './ChatSystemMessage.module.scss'; -interface Props { +export type ChatSystemMessageProps = { message: ChatMessage; highlightString: string; -} -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function ChatSystemMessage({ message, highlightString }: Props) { - const { body, user } = message; - const { displayName } = user; +}; - return ( -
-
- {displayName} -
- -
- +export const ChatSystemMessage: FC = ({ + message: { + body, + user: { displayName }, + }, + highlightString, +}) => ( +
+
+ {displayName}
- ); -} + +
+ +
+); diff --git a/web/components/chat/ChatTextField/ChatTextField.stories.tsx b/web/components/chat/ChatTextField/ChatTextField.stories.tsx index 2df1bf414..ea3da2df4 100644 --- a/web/components/chat/ChatTextField/ChatTextField.stories.tsx +++ b/web/components/chat/ChatTextField/ChatTextField.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import ChatTextField from './ChatTextField'; +import { ChatTextField } from './ChatTextField'; import Mockup from '../../../stories/assets/mocks/chatinput-mock.png'; const mockResponse = JSON.parse( diff --git a/web/components/chat/ChatTextField/ChatTextField.tsx b/web/components/chat/ChatTextField/ChatTextField.tsx index 3acf9a1c3..e672281bf 100644 --- a/web/components/chat/ChatTextField/ChatTextField.tsx +++ b/web/components/chat/ChatTextField/ChatTextField.tsx @@ -1,14 +1,14 @@ import { SendOutlined, SmileOutlined } from '@ant-design/icons'; import { Button, Popover } from 'antd'; -import React, { useMemo, useState } from 'react'; +import React, { FC, useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { Editor, Node, Path, Transforms, createEditor, BaseEditor, Text, Descendant } from 'slate'; +import { Transforms, createEditor, BaseEditor, Text, Descendant, Editor, Node, Path } from 'slate'; import { Slate, Editable, withReact, ReactEditor, useSelected, useFocused } from 'slate-react'; -import EmojiPicker from './EmojiPicker'; +import { EmojiPicker } from './EmojiPicker'; import WebsocketService from '../../../services/websocket-service'; import { websocketServiceAtom } from '../../stores/ClientConfigStore'; import { MessageType } from '../../../interfaces/socket-events'; -import style from './ChatTextField.module.scss'; +import styles from './ChatTextField.module.scss'; type CustomElement = { type: 'paragraph' | 'span'; children: CustomText[] } | ImageNode; type CustomText = { text: string }; @@ -90,7 +90,9 @@ const serialize = node => { } }; -export default function ChatTextField() { +export type ChatTextFieldProps = {}; + +export const ChatTextField: FC = () => { const [showEmojis, setShowEmojis] = useState(false); const websocketService = useRecoilValue(websocketServiceAtom); const editor = useMemo(() => withReact(withImages(createEditor())), []); @@ -196,14 +198,13 @@ export default function ChatTextField() { return (
-
+
setValue(change.value)} autoFocus /> setShowEmojis(!showEmojis)} >
); -} +}; diff --git a/web/components/chat/ChatTextField/EmojiPicker.tsx b/web/components/chat/ChatTextField/EmojiPicker.tsx index 115b71bd8..cd7b8a192 100644 --- a/web/components/chat/ChatTextField/EmojiPicker.tsx +++ b/web/components/chat/ChatTextField/EmojiPicker.tsx @@ -9,7 +9,7 @@ interface Props { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function EmojiPicker(props: Props) { +export const EmojiPicker = (props: Props) => { const [customEmoji, setCustomEmoji] = useState([]); const { onEmojiSelect, onCustomEmojiSelect } = props; const ref = useRef(); @@ -54,4 +54,4 @@ export default function EmojiPicker(props: Props) { }, [customEmoji]); return
; -} +}; diff --git a/web/components/chat/ChatUserBadge/ChatUserBadge.stories.tsx b/web/components/chat/ChatUserBadge/ChatUserBadge.stories.tsx index 8b76eae0b..3d0657ab5 100644 --- a/web/components/chat/ChatUserBadge/ChatUserBadge.stories.tsx +++ b/web/components/chat/ChatUserBadge/ChatUserBadge.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ChatUserBadge from './ChatUserBadge'; +import { ChatUserBadge } from './ChatUserBadge'; export default { title: 'owncast/Chat/Messages/User Flag', diff --git a/web/components/chat/ChatUserBadge/ChatUserBadge.tsx b/web/components/chat/ChatUserBadge/ChatUserBadge.tsx index 7d02fb0ef..d00906987 100644 --- a/web/components/chat/ChatUserBadge/ChatUserBadge.tsx +++ b/web/components/chat/ChatUserBadge/ChatUserBadge.tsx @@ -1,19 +1,18 @@ -import React from 'react'; -import s from './ChatUserBadge.module.scss'; +import React, { FC } from 'react'; +import styles from './ChatUserBadge.module.scss'; -interface Props { +export type ChatUserBadgeProps = { badge: React.ReactNode; userColor: number; -} +}; -export default function ChatUserBadge(props: Props) { - const { badge, userColor } = props; +export const ChatUserBadge: FC = ({ badge, userColor }) => { const color = `var(--theme-user-colors-${userColor})`; const style = { color, borderColor: color }; return ( - + {badge} ); -} +}; diff --git a/web/components/chat/ChatUserMessage/ChatUserMessage.stories.tsx b/web/components/chat/ChatUserMessage/ChatUserMessage.stories.tsx index 99cac6a83..91ad0091c 100644 --- a/web/components/chat/ChatUserMessage/ChatUserMessage.stories.tsx +++ b/web/components/chat/ChatUserMessage/ChatUserMessage.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import ChatUserMessage from './index'; +import { ChatUserMessage } from './ChatUserMessage'; import { ChatMessage } from '../../../interfaces/chat-message.model'; import Mock from '../../../stories/assets/mocks/chatmessage-user.png'; diff --git a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx index 56376b10f..15c4ad701 100644 --- a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx +++ b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx @@ -1,19 +1,19 @@ /* eslint-disable react/no-danger */ -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { Highlight } from 'react-highlighter-ts'; import he from 'he'; import cn from 'classnames'; import { Tooltip } from 'antd'; import { LinkOutlined } from '@ant-design/icons'; import { useRecoilValue } from 'recoil'; -import s from './ChatUserMessage.module.scss'; +import styles from './ChatUserMessage.module.scss'; import { formatTimestamp } from './messageFmt'; import { ChatMessage } from '../../../interfaces/chat-message.model'; -import ChatModerationActionMenu from '../ChatModerationActionMenu/ChatModerationActionMenu'; -import ChatUserBadge from '../ChatUserBadge/ChatUserBadge'; +import { ChatModerationActionMenu } from '../ChatModerationActionMenu/ChatModerationActionMenu'; +import { ChatUserBadge } from '../ChatUserBadge/ChatUserBadge'; import { accessTokenAtom } from '../../stores/ClientConfigStore'; -interface Props { +export type ChatUserMessageProps = { message: ChatMessage; showModeratorMenu: boolean; highlightString: string; @@ -21,9 +21,9 @@ interface Props { sameUserAsLast: boolean; isAuthorModerator: boolean; isAuthorAuthenticated: boolean; -} +}; -export default function ChatUserMessage({ +export const ChatUserMessage: FC = ({ message, highlightString, showModeratorMenu, @@ -31,7 +31,7 @@ export default function ChatUserMessage({ sameUserAsLast, isAuthorModerator, isAuthorAuthenticated, -}: Props) { +}) => { const { id: messageId, body, user, timestamp } = message; const { id: userId, displayName, displayColor } = user; const accessToken = useRecoilValue(accessTokenAtom); @@ -59,29 +59,32 @@ export default function ChatUserMessage({ }, [message]); return ( -
+
{!sameUserAsLast && ( -
- {displayName} +
+ {displayName} {badgeNodes}
)} -
+
{showModeratorMenu && ( -
+
)} -
-
+
+
); -} +}; diff --git a/web/components/chat/ChatUserMessage/index.ts b/web/components/chat/ChatUserMessage/index.ts deleted file mode 100644 index d715649d0..000000000 --- a/web/components/chat/ChatUserMessage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ChatUserMessage'; diff --git a/web/components/chat/index.ts b/web/components/chat/index.ts deleted file mode 100644 index e7149bbc5..000000000 --- a/web/components/chat/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as ChatContainer } from './ChatContainer'; -export { default as ChatUserMessage } from './ChatUserMessage'; -export { default as ChatTextField } from './ChatTextField/ChatTextField'; diff --git a/web/components/common/ContentHeader/ContentHeader.stories.tsx b/web/components/common/ContentHeader/ContentHeader.stories.tsx index 474efccc6..dd313ffb2 100644 --- a/web/components/common/ContentHeader/ContentHeader.stories.tsx +++ b/web/components/common/ContentHeader/ContentHeader.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import ContentHeader from './ContentHeader'; +import { ContentHeader } from './ContentHeader'; export default { title: 'owncast/Components/Content Header', diff --git a/web/components/common/ContentHeader/ContentHeader.tsx b/web/components/common/ContentHeader/ContentHeader.tsx index 02ac5a609..0fa6b55e3 100644 --- a/web/components/common/ContentHeader/ContentHeader.tsx +++ b/web/components/common/ContentHeader/ContentHeader.tsx @@ -1,36 +1,42 @@ import cn from 'classnames'; - -import { ServerLogo } from '../../ui'; -import SocialLinks from '../../ui/SocialLinks/SocialLinks'; +import { FC } from 'react'; +import { Logo } from '../../ui/Logo/Logo'; +import { SocialLinks } from '../../ui/SocialLinks/SocialLinks'; import { SocialLink } from '../../../interfaces/social-link.model'; -import s from './ContentHeader.module.scss'; +import styles from './ContentHeader.module.scss'; -interface Props { +export type ContentHeaderProps = { name: string; title: string; summary: string; tags: string[]; links: SocialLink[]; logo: string; -} -export default function ContentHeader({ name, title, summary, logo, tags, links }: Props) { - return ( -
-
-
- +}; + +export const ContentHeader: FC = ({ + name, + title, + summary, + logo, + tags, + links, +}) => ( +
+
+
+ +
+
+
{name}
+
{title || summary}
+
+ {tags.length > 0 && tags.map(tag => #{tag} )}
-
-
{name}
-
{title || summary}
-
- {tags.length > 0 && tags.map(tag => #{tag} )} -
-
- -
+
+
- ); -} +
+); diff --git a/web/components/common/ContentHeader/index.ts b/web/components/common/ContentHeader/index.ts deleted file mode 100644 index a75da8590..000000000 --- a/web/components/common/ContentHeader/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ContentHeader'; diff --git a/web/components/common/Logo/index.ts b/web/components/common/Logo/index.ts deleted file mode 100644 index a5be7785e..000000000 --- a/web/components/common/Logo/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Logo'; diff --git a/web/components/common/Logo/Logo.module.scss b/web/components/common/OwncastLogo/OwncastLogo.module.scss similarity index 100% rename from web/components/common/Logo/Logo.module.scss rename to web/components/common/OwncastLogo/OwncastLogo.module.scss diff --git a/web/components/common/Logo/Logo.stories.tsx b/web/components/common/OwncastLogo/OwncastLogo.stories.tsx similarity index 93% rename from web/components/common/Logo/Logo.stories.tsx rename to web/components/common/OwncastLogo/OwncastLogo.stories.tsx index f0f13f1c2..7d831eaab 100644 --- a/web/components/common/Logo/Logo.stories.tsx +++ b/web/components/common/OwncastLogo/OwncastLogo.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import OwncastLogo from './Logo'; +import { OwncastLogo } from './OwncastLogo'; export default { title: 'owncast/Components/Header Logo', diff --git a/web/components/common/Logo/Logo.tsx b/web/components/common/OwncastLogo/OwncastLogo.tsx similarity index 96% rename from web/components/common/Logo/Logo.tsx rename to web/components/common/OwncastLogo/OwncastLogo.tsx index 3733f022f..3eebe865d 100644 --- a/web/components/common/Logo/Logo.tsx +++ b/web/components/common/OwncastLogo/OwncastLogo.tsx @@ -1,15 +1,15 @@ -import React from 'react'; +import React, { FC } from 'react'; import cn from 'classnames'; -import s from './Logo.module.scss'; +import styles from './OwncastLogo.module.scss'; -interface Props { +export type LogoProps = { variant: 'simple' | 'contrast'; -} +}; -export default function Logo({ variant = 'simple' }: Props) { - const rootClassName = cn(s.root, { - [s.simple]: variant === 'simple', - [s.contrast]: variant === 'contrast', +export const OwncastLogo: FC = ({ variant = 'simple' }) => { + const rootClassName = cn(styles.root, { + [styles.simple]: variant === 'simple', + [styles.contrast]: variant === 'contrast', }); return ( @@ -169,4 +169,4 @@ export default function Logo({ variant = 'simple' }: Props) {
); -} +}; diff --git a/web/components/common/UserDropdown/UserDropdown.stories.tsx b/web/components/common/UserDropdown/UserDropdown.stories.tsx index 6bd45123c..f4543d798 100644 --- a/web/components/common/UserDropdown/UserDropdown.stories.tsx +++ b/web/components/common/UserDropdown/UserDropdown.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import UserDropdown from './UserDropdown'; +import { UserDropdown } from './UserDropdown'; export default { title: 'owncast/Components/User settings menu', diff --git a/web/components/common/UserDropdown/UserDropdown.tsx b/web/components/common/UserDropdown/UserDropdown.tsx index 9b60472e7..c4a40b438 100644 --- a/web/components/common/UserDropdown/UserDropdown.tsx +++ b/web/components/common/UserDropdown/UserDropdown.tsx @@ -7,24 +7,24 @@ import { UserOutlined, } from '@ant-design/icons'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { useState } from 'react'; +import { FC, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import Modal from '../../ui/Modal/Modal'; +import { Modal } from '../../ui/Modal/Modal'; import { chatVisibleToggleAtom, chatDisplayNameAtom, appStateAtom, } from '../../stores/ClientConfigStore'; -import s from './UserDropdown.module.scss'; -import NameChangeModal from '../../modals/NameChangeModal/NameChangeModal'; +import styles from './UserDropdown.module.scss'; +import { NameChangeModal } from '../../modals/NameChangeModal/NameChangeModal'; import { AppStateOptions } from '../../stores/application-state'; -import AuthModal from '../../modals/AuthModal/AuthModal'; +import { AuthModal } from '../../modals/AuthModal/AuthModal'; -interface Props { +export type UserDropdownProps = { username?: string; -} +}; -export default function UserDropdown({ username: defaultUsername }: Props) { +export const UserDropdown: FC = ({ username: defaultUsername = undefined }) => { const username = defaultUsername || useRecoilValue(chatDisplayNameAtom); const [showNameChangeModal, setShowNameChangeModal] = useState(false); const [showAuthModal, setShowAuthModal] = useState(false); @@ -66,7 +66,7 @@ export default function UserDropdown({ username: defaultUsername }: Props) { ); return ( -
+
); -} - -UserDropdown.defaultProps = { - username: undefined, }; diff --git a/web/components/common/UserDropdown/index.ts b/web/components/common/UserDropdown/index.ts deleted file mode 100644 index d94f0db06..000000000 --- a/web/components/common/UserDropdown/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './UserDropdown'; diff --git a/web/components/common/index.ts b/web/components/common/index.ts deleted file mode 100644 index b864c8750..000000000 --- a/web/components/common/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as UserDropdown } from './UserDropdown'; -export { default as OwncastLogo } from './Logo'; diff --git a/web/components/config/video-codec-selector.tsx b/web/components/config/CodecSelector.tsx similarity index 96% rename from web/components/config/video-codec-selector.tsx rename to web/components/config/CodecSelector.tsx index 628d34a3b..de71dbfe3 100644 --- a/web/components/config/video-codec-selector.tsx +++ b/web/components/config/CodecSelector.tsx @@ -1,5 +1,5 @@ import { Popconfirm, Select, Typography } from 'antd'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { FC, useContext, useEffect, useState } from 'react'; import { AlertMessageContext } from '../../utils/alert-message-context'; import { API_VIDEO_CODEC, @@ -13,10 +13,11 @@ import { STATUS_SUCCESS, } from '../../utils/input-statuses'; import { ServerStatusContext } from '../../utils/server-status-context'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; -// eslint-disable-next-line react/function-component-definition -export default function CodecSelector() { +export type CodecSelectorProps = {}; + +export const CodecSelector: FC = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { videoCodec, supportedCodecs } = serverConfig || {}; @@ -170,4 +171,4 @@ export default function CodecSelector() {
); -} +}; diff --git a/web/components/config/video-variants-table.tsx b/web/components/config/CurrentVariantsTable.tsx similarity index 96% rename from web/components/config/video-variants-table.tsx rename to web/components/config/CurrentVariantsTable.tsx index 478d6c730..e93916dff 100644 --- a/web/components/config/video-variants-table.tsx +++ b/web/components/config/CurrentVariantsTable.tsx @@ -1,6 +1,6 @@ // Updating a variant will post ALL the variants in an array as an update to the API. -import React, { useContext, useState } from 'react'; +import React, { FC, useContext, useState } from 'react'; import { Typography, Table, Modal, Button, Alert } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { DeleteOutlined } from '@ant-design/icons'; @@ -8,7 +8,7 @@ import { ServerStatusContext } from '../../utils/server-status-context'; import { AlertMessageContext } from '../../utils/alert-message-context'; import { UpdateArgs, VideoVariant } from '../../types/config-section'; -import VideoVariantForm from './video-variant-form'; +import { VideoVariantForm } from './VideoVariantForm'; import { API_VIDEO_VARIANTS, DEFAULT_VARIANT_STATE, @@ -24,11 +24,11 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; const { Title } = Typography; -export default function CurrentVariantsTable() { +export const CurrentVariantsTable: FC = () => { const [displayModal, setDisplayModal] = useState(false); const [modalProcessing, setModalProcessing] = useState(false); const [editId, setEditId] = useState(0); @@ -242,4 +242,4 @@ export default function CurrentVariantsTable() { ); -} +}; diff --git a/web/components/config/edit-custom-css.tsx b/web/components/config/EditCustomStyles.tsx similarity index 93% rename from web/components/config/edit-custom-css.tsx rename to web/components/config/EditCustomStyles.tsx index 8134a9759..8857b8c36 100644 --- a/web/components/config/edit-custom-css.tsx +++ b/web/components/config/EditCustomStyles.tsx @@ -1,5 +1,5 @@ // EDIT CUSTOM CSS STYLES -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect, useContext, FC } from 'react'; import { Typography, Button } from 'antd'; import { ServerStatusContext } from '../../utils/server-status-context'; @@ -15,14 +15,13 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; - -import TextField, { TEXTFIELD_TYPE_TEXTAREA } from './form-textfield'; +import { FormStatusIndicator } from './FormStatusIndicator'; +import { TextField, TEXTFIELD_TYPE_TEXTAREA } from './TextField'; import { UpdateArgs } from '../../types/config-section'; const { Title } = Typography; -export default function EditCustomStyles() { +export const EditCustomStyles: FC = () => { const [content, setContent] = useState(''); const [submitStatus, setSubmitStatus] = useState(null); const [hasChanged, setHasChanged] = useState(false); @@ -114,4 +113,4 @@ export default function EditCustomStyles() {
); -} +}; diff --git a/web/components/config/edit-instance-details.tsx b/web/components/config/EditInstanceDetails.tsx similarity index 94% rename from web/components/config/edit-instance-details.tsx rename to web/components/config/EditInstanceDetails.tsx index d2c672cfe..34e9defab 100644 --- a/web/components/config/edit-instance-details.tsx +++ b/web/components/config/EditInstanceDetails.tsx @@ -1,11 +1,10 @@ -import React, { useState, useContext, useEffect } from 'react'; +import React, { useState, useContext, useEffect, FC } from 'react'; import { Typography } from 'antd'; - -import TextFieldWithSubmit, { +import { + TextFieldWithSubmit, TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL, -} from './form-textfield-with-submit'; - +} from './TextFieldWithSubmit'; import { ServerStatusContext } from '../../utils/server-status-context'; import { postConfigUpdateToAPI, @@ -18,14 +17,13 @@ import { FIELD_PROPS_NSFW, FIELD_PROPS_HIDE_VIEWER_COUNT, } from '../../utils/config-constants'; - import { UpdateArgs } from '../../types/config-section'; -import ToggleSwitch from './form-toggleswitch'; -import EditLogo from './edit-logo'; +import { ToggleSwitch } from './ToggleSwitch'; +import { EditLogo } from './EditLogo'; const { Title } = Typography; -export default function EditInstanceDetails() { +export const EditInstanceDetails: FC = () => { const [formDataValues, setFormDataValues] = useState(null); const serverStatusData = useContext(ServerStatusContext); const { serverConfig } = serverStatusData || {}; @@ -163,4 +161,4 @@ export default function EditInstanceDetails() {
); -} +}; diff --git a/web/components/config/edit-server-details.tsx b/web/components/config/EditInstanceDetails2.tsx similarity index 95% rename from web/components/config/edit-server-details.tsx rename to web/components/config/EditInstanceDetails2.tsx index 3373953f0..dbcca5739 100644 --- a/web/components/config/edit-server-details.tsx +++ b/web/components/config/EditInstanceDetails2.tsx @@ -1,17 +1,10 @@ import React, { useState, useContext, useEffect } from 'react'; import { Button, Tooltip, Collapse, Typography } from 'antd'; import { CopyOutlined, RedoOutlined } from '@ant-design/icons'; - -import { - TEXTFIELD_TYPE_NUMBER, - TEXTFIELD_TYPE_PASSWORD, - TEXTFIELD_TYPE_URL, -} from './form-textfield'; -import TextFieldWithSubmit from './form-textfield-with-submit'; - +import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD, TEXTFIELD_TYPE_URL } from './TextField'; +import { TextFieldWithSubmit } from './TextFieldWithSubmit'; import { ServerStatusContext } from '../../utils/server-status-context'; import { AlertMessageContext } from '../../utils/alert-message-context'; - import { TEXTFIELD_PROPS_FFMPEG, TEXTFIELD_PROPS_RTMP_PORT, @@ -19,13 +12,12 @@ import { TEXTFIELD_PROPS_STREAM_KEY, TEXTFIELD_PROPS_WEB_PORT, } from '../../utils/config-constants'; - import { UpdateArgs } from '../../types/config-section'; -import ResetYP from './reset-yp'; +import { ResetYP } from './ResetYP'; const { Panel } = Collapse; -export default function EditInstanceDetails() { +export const EditInstanceDetails = () => { const [formDataValues, setFormDataValues] = useState(null); const serverStatusData = useContext(ServerStatusContext); const { setMessage } = useContext(AlertMessageContext); @@ -164,4 +156,4 @@ export default function EditInstanceDetails() {
); -} +}; diff --git a/web/components/config/edit-tags.tsx b/web/components/config/EditInstanceTags.tsx similarity index 94% rename from web/components/config/edit-tags.tsx rename to web/components/config/EditInstanceTags.tsx index 5a1ea6c93..fc840e61c 100644 --- a/web/components/config/edit-tags.tsx +++ b/web/components/config/EditInstanceTags.tsx @@ -1,14 +1,13 @@ /* eslint-disable react/no-array-index-key */ -import React, { useContext, useState, useEffect } from 'react'; +import React, { useContext, useState, useEffect, FC } from 'react'; import { Typography, Tag } from 'antd'; - import { ServerStatusContext } from '../../utils/server-status-context'; import { FIELD_PROPS_TAGS, RESET_TIMEOUT, postConfigUpdateToAPI, } from '../../utils/config-constants'; -import TextField from './form-textfield'; +import { TextField } from './TextField'; import { UpdateArgs } from '../../types/config-section'; import { createInputStatus, @@ -18,11 +17,11 @@ import { STATUS_SUCCESS, STATUS_WARNING, } from '../../utils/input-statuses'; -import { TAG_COLOR } from './edit-string-array'; +import { TAG_COLOR } from './EditValueArray'; const { Title } = Typography; -export default function EditInstanceTags() { +export const EditInstanceTags: FC = () => { const [newTagInput, setNewTagInput] = useState(''); const [submitStatus, setSubmitStatus] = useState(null); @@ -136,4 +135,4 @@ export default function EditInstanceTags() {
); -} +}; diff --git a/web/components/config/edit-logo.tsx b/web/components/config/EditLogo.tsx similarity index 96% rename from web/components/config/edit-logo.tsx rename to web/components/config/EditLogo.tsx index 1698b2336..658c8d061 100644 --- a/web/components/config/edit-logo.tsx +++ b/web/components/config/EditLogo.tsx @@ -1,8 +1,8 @@ import { Button, Upload } from 'antd'; import { RcFile } from 'antd/lib/upload/interface'; import { LoadingOutlined, UploadOutlined } from '@ant-design/icons'; -import React, { useState, useContext } from 'react'; -import FormStatusIndicator from './form-status-indicator'; +import React, { useState, useContext, FC } from 'react'; +import { FormStatusIndicator } from './FormStatusIndicator'; import { ServerStatusContext } from '../../utils/server-status-context'; import { postConfigUpdateToAPI, @@ -26,7 +26,7 @@ function getBase64(img: File | Blob, callback: (imageUrl: string | ArrayBuffer) reader.readAsDataURL(img); } -export default function EditLogo() { +export const EditLogo: FC = () => { const [logoUrl, setlogoUrl] = useState(null); const [loading, setLoading] = useState(false); const [logoCachedbuster, setLogoCacheBuster] = useState(0); @@ -125,4 +125,4 @@ export default function EditLogo() {
); -} +}; diff --git a/web/components/config/edit-page-content.tsx b/web/components/config/EditPageContent.tsx similarity index 95% rename from web/components/config/edit-page-content.tsx rename to web/components/config/EditPageContent.tsx index 4e2c005ab..51b34cd3c 100644 --- a/web/components/config/edit-page-content.tsx +++ b/web/components/config/EditPageContent.tsx @@ -1,5 +1,5 @@ // EDIT CUSTOM DETAILS ON YOUR PAGE -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect, useContext, FC } from 'react'; import { Typography, Button } from 'antd'; import dynamic from 'next/dynamic'; import MarkdownIt from 'markdown-it'; @@ -17,7 +17,7 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; import 'react-markdown-editor-lite/lib/index.css'; @@ -28,7 +28,7 @@ const MdEditor = dynamic(() => import('react-markdown-editor-lite'), { const { Title } = Typography; -export default function EditPageContent() { +export const EditPageContent: FC = () => { const [content, setContent] = useState(''); const [submitStatus, setSubmitStatus] = useState(null); const [hasChanged, setHasChanged] = useState(false); @@ -122,4 +122,4 @@ export default function EditPageContent() {
); -} +}; diff --git a/web/components/config/edit-social-links.tsx b/web/components/config/EditSocialLinks.tsx similarity index 97% rename from web/components/config/edit-social-links.tsx rename to web/components/config/EditSocialLinks.tsx index d92c8f351..d3f905cab 100644 --- a/web/components/config/edit-social-links.tsx +++ b/web/components/config/EditSocialLinks.tsx @@ -1,8 +1,8 @@ -import React, { useState, useContext, useEffect } from 'react'; +import React, { useState, useContext, useEffect, FC } from 'react'; import { Typography, Table, Button, Modal, Input } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { DeleteOutlined } from '@ant-design/icons'; -import SocialDropdown from './social-icons-dropdown'; +import { SocialDropdown } from './SocialDropdown'; import { fetchData, SOCIAL_PLATFORMS_LIST, NEXT_PUBLIC_API_HOST } from '../../utils/apis'; import { ServerStatusContext } from '../../utils/server-status-context'; import { @@ -14,13 +14,13 @@ import { } from '../../utils/config-constants'; import { SocialHandle, UpdateArgs } from '../../types/config-section'; import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls'; -import TextField from './form-textfield'; +import { TextField } from './TextField'; import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; const { Title } = Typography; -export default function EditSocialLinks() { +export const EditSocialLinks: FC = () => { const [availableIconsList, setAvailableIconsList] = useState([]); const [currentSocialHandles, setCurrentSocialHandles] = useState([]); @@ -316,4 +316,4 @@ export default function EditSocialLinks() {
); -} +}; diff --git a/web/components/config/edit-storage.tsx b/web/components/config/EditStorage.tsx similarity index 96% rename from web/components/config/edit-storage.tsx rename to web/components/config/EditStorage.tsx index f717bfba9..3eae1db6d 100644 --- a/web/components/config/edit-storage.tsx +++ b/web/components/config/EditStorage.tsx @@ -1,6 +1,6 @@ import { Button, Collapse } from 'antd'; import classNames from 'classnames'; -import React, { useContext, useState, useEffect } from 'react'; +import React, { useContext, useState, useEffect, FC } from 'react'; import { UpdateArgs } from '../../types/config-section'; import { ServerStatusContext } from '../../utils/server-status-context'; import { AlertMessageContext } from '../../utils/alert-message-context'; @@ -18,10 +18,10 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import TextField from './form-textfield'; -import FormStatusIndicator from './form-status-indicator'; +import { TextField } from './TextField'; +import { FormStatusIndicator } from './FormStatusIndicator'; import isValidUrl from '../../utils/urls'; -import ToggleSwitch from './form-toggleswitch'; +import { ToggleSwitch } from './ToggleSwitch'; const { Panel } = Collapse; @@ -63,7 +63,7 @@ function checkSaveable(formValues: any, currentValues: any) { return false; } -export default function EditStorage() { +export const EditStorage: FC = () => { const [formDataValues, setFormDataValues] = useState(null); const [submitStatus, setSubmitStatus] = useState(null); @@ -254,4 +254,4 @@ export default function EditStorage() {
); -} +}; diff --git a/web/components/config/edit-string-array.tsx b/web/components/config/EditValueArray.tsx similarity index 82% rename from web/components/config/edit-string-array.tsx rename to web/components/config/EditValueArray.tsx index 65c50a6d7..793bb7c90 100644 --- a/web/components/config/edit-string-array.tsx +++ b/web/components/config/EditValueArray.tsx @@ -1,17 +1,17 @@ /* eslint-disable react/no-array-index-key */ -import React, { useState } from 'react'; +import React, { FC, useState } from 'react'; import { Typography, Tag } from 'antd'; -import TextField from './form-textfield'; +import { TextField } from './TextField'; import { UpdateArgs } from '../../types/config-section'; import { StatusState } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; const { Title } = Typography; export const TAG_COLOR = '#5a67d8'; -interface EditStringArrayProps { +export type EditStringArrayProps = { title: string; description?: string; placeholder: string; @@ -21,21 +21,20 @@ interface EditStringArrayProps { continuousStatusMessage?: StatusState; handleDeleteIndex: (index: number) => void; handleCreateString: (arg: string) => void; -} +}; -export default function EditValueArray(props: EditStringArrayProps) { +export const EditValueArray: FC = ({ + title, + description, + placeholder, + maxLength, + values, + handleDeleteIndex, + handleCreateString, + submitStatus, + continuousStatusMessage, +}) => { const [newStringInput, setNewStringInput] = useState(''); - const { - title, - description, - placeholder, - maxLength, - values, - handleDeleteIndex, - handleCreateString, - submitStatus, - continuousStatusMessage, - } = props; const handleInputChange = ({ value }: UpdateArgs) => { setNewStringInput(value); @@ -84,7 +83,7 @@ export default function EditValueArray(props: EditStringArrayProps) {
); -} +}; EditValueArray.defaultProps = { maxLength: 50, diff --git a/web/components/config/edit-directory.tsx b/web/components/config/EditYPDetails.tsx similarity index 92% rename from web/components/config/edit-directory.tsx rename to web/components/config/EditYPDetails.tsx index 558a4e900..13e39dcfc 100644 --- a/web/components/config/edit-directory.tsx +++ b/web/components/config/EditYPDetails.tsx @@ -1,15 +1,14 @@ // Note: references to "yp" in the app are likely related to Owncast Directory -import React, { useState, useContext, useEffect } from 'react'; +import React, { useState, useContext, useEffect, FC } from 'react'; import { Typography } from 'antd'; -import ToggleSwitch from './form-toggleswitch'; - +import { ToggleSwitch } from './ToggleSwitch'; import { ServerStatusContext } from '../../utils/server-status-context'; import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from '../../utils/config-constants'; const { Title } = Typography; -export default function EditYPDetails() { +export const EditYPDetails: FC = () => { const [formDataValues, setFormDataValues] = useState(null); const serverStatusData = useContext(ServerStatusContext); @@ -68,4 +67,4 @@ export default function EditYPDetails() {
); -} +}; diff --git a/web/components/config/form-status-indicator.tsx b/web/components/config/FormStatusIndicator.tsx similarity index 70% rename from web/components/config/form-status-indicator.tsx rename to web/components/config/FormStatusIndicator.tsx index 8d1fb38ee..08d8cd5b5 100644 --- a/web/components/config/form-status-indicator.tsx +++ b/web/components/config/FormStatusIndicator.tsx @@ -1,12 +1,13 @@ -import React from 'react'; +import React, { FC } from 'react'; import classNames from 'classnames'; import { StatusState } from '../../utils/input-statuses'; -interface FormStatusIndicatorProps { +export type FormStatusIndicatorProps = { status: StatusState; -} -export default function FormStatusIndicator({ status }: FormStatusIndicatorProps) { +}; + +export const FormStatusIndicator: FC = ({ status }) => { const { type, icon, message } = status || {}; const classes = classNames({ 'status-container': true, @@ -19,4 +20,5 @@ export default function FormStatusIndicator({ status }: FormStatusIndicatorProps {message ? {message} : null} ); -} +}; +export default FormStatusIndicator; diff --git a/web/components/config/reset-yp.tsx b/web/components/config/ResetYP.tsx similarity index 93% rename from web/components/config/reset-yp.tsx rename to web/components/config/ResetYP.tsx index e40f12da6..3022ba2a2 100644 --- a/web/components/config/reset-yp.tsx +++ b/web/components/config/ResetYP.tsx @@ -1,5 +1,5 @@ import { Popconfirm, Button, Typography } from 'antd'; -import { useContext, useState } from 'react'; +import { FC, useContext, useState } from 'react'; import { AlertMessageContext } from '../../utils/alert-message-context'; import { API_YP_RESET, fetchData } from '../../utils/apis'; @@ -10,9 +10,9 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; -export default function ResetYP() { +export const ResetYP: FC = () => { const { setMessage } = useContext(AlertMessageContext); const [submitStatus, setSubmitStatus] = useState(null); @@ -61,4 +61,4 @@ export default function ResetYP() {

); -} +}; diff --git a/web/components/config/social-icons-dropdown.tsx b/web/components/config/SocialDropdown.tsx similarity index 92% rename from web/components/config/social-icons-dropdown.tsx rename to web/components/config/SocialDropdown.tsx index feb540655..4399fb32e 100644 --- a/web/components/config/social-icons-dropdown.tsx +++ b/web/components/config/SocialDropdown.tsx @@ -1,16 +1,16 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Select } from 'antd'; import { SocialHandleDropdownItem } from '../../types/config-section'; import { NEXT_PUBLIC_API_HOST } from '../../utils/apis'; import { OTHER_SOCIAL_HANDLE_OPTION } from '../../utils/config-constants'; -interface DropdownProps { +export type DropdownProps = { iconList: SocialHandleDropdownItem[]; selectedOption: string; onSelected: any; -} +}; -export default function SocialDropdown({ iconList, selectedOption, onSelected }: DropdownProps) { +export const SocialDropdown: FC = ({ iconList, selectedOption, onSelected }) => { const handleSelected = (value: string) => { if (onSelected) { onSelected(value); @@ -62,4 +62,4 @@ export default function SocialDropdown({ iconList, selectedOption, onSelected }:
); -} +}; diff --git a/web/components/config/form-textfield.tsx b/web/components/config/TextField.tsx similarity index 90% rename from web/components/config/form-textfield.tsx rename to web/components/config/TextField.tsx index 5e5a31bf1..cc9b37721 100644 --- a/web/components/config/form-textfield.tsx +++ b/web/components/config/TextField.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import React, { FC } from 'react'; import classNames from 'classnames'; import { Input, InputNumber } from 'antd'; import { FieldUpdaterFunc } from '../../types/config-section'; // import InfoTip from '../info-tip'; import { StatusState } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password @@ -12,7 +12,7 @@ export const TEXTFIELD_TYPE_NUMBER = 'numeric'; // InputNumber export const TEXTFIELD_TYPE_TEXTAREA = 'textarea'; // Input.TextArea export const TEXTFIELD_TYPE_URL = 'url'; -export interface TextFieldProps { +export type TextFieldProps = { fieldName: string; onSubmit?: () => void; @@ -33,28 +33,26 @@ export interface TextFieldProps { value?: string | number; onBlur?: FieldUpdaterFunc; onChange?: FieldUpdaterFunc; -} - -export default function TextField(props: TextFieldProps) { - const { - className, - disabled, - fieldName, - label, - maxLength, - onBlur, - onChange, - onPressEnter, - pattern, - placeholder, - required, - status, - tip, - type, - useTrim, - value, - } = props; +}; +export const TextField: FC = ({ + className, + disabled, + fieldName, + label, + maxLength, + onBlur, + onChange, + onPressEnter, + pattern, + placeholder, + required, + status, + tip, + type, + useTrim, + value, +}) => { const handleChange = (e: any) => { // if an extra onChange handler was sent in as a prop, let's run that too. if (onChange) { @@ -151,7 +149,8 @@ export default function TextField(props: TextFieldProps) {
); -} +}; +export default TextField; TextField.defaultProps = { className: '', diff --git a/web/components/config/form-textfield-with-submit.tsx b/web/components/config/TextFieldWithSubmit.tsx similarity index 90% rename from web/components/config/form-textfield-with-submit.tsx rename to web/components/config/TextFieldWithSubmit.tsx index 23a9175ad..ce89c7030 100644 --- a/web/components/config/form-textfield-with-submit.tsx +++ b/web/components/config/TextFieldWithSubmit.tsx @@ -1,6 +1,6 @@ import { Button } from 'antd'; import classNames from 'classnames'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { FC, useContext, useEffect, useState } from 'react'; import { UpdateArgs } from '../../types/config-section'; import { postConfigUpdateToAPI, RESET_TIMEOUT } from '../../utils/config-constants'; import { @@ -11,8 +11,8 @@ import { STATUS_SUCCESS, } from '../../utils/input-statuses'; import { ServerStatusContext } from '../../utils/server-status-context'; -import FormStatusIndicator from './form-status-indicator'; -import TextField, { TextFieldProps } from './form-textfield'; +import { FormStatusIndicator } from './FormStatusIndicator'; +import { TextField, TextFieldProps } from './TextField'; export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password @@ -20,13 +20,20 @@ export const TEXTFIELD_TYPE_NUMBER = 'numeric'; export const TEXTFIELD_TYPE_TEXTAREA = 'textarea'; export const TEXTFIELD_TYPE_URL = 'url'; -interface TextFieldWithSubmitProps extends TextFieldProps { +export type TextFieldWithSubmitProps = TextFieldProps & { apiPath: string; configPath?: string; initialValue?: string; -} +}; -export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { +export const TextFieldWithSubmit: FC = ({ + apiPath, + configPath = '', + initialValue, + useTrim, + useTrimLead, + ...textFieldProps // rest of props +}) => { const [submitStatus, setSubmitStatus] = useState(null); const [hasChanged, setHasChanged] = useState(false); @@ -36,15 +43,6 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { let resetTimer = null; - const { - apiPath, - configPath = '', - initialValue, - useTrim, - useTrimLead, - ...textFieldProps // rest of props - } = props; - const { fieldName, required, tip, status, value, onChange, onSubmit } = textFieldProps; // Clear out any validation states and messaging @@ -150,7 +148,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { ); -} +}; TextFieldWithSubmit.defaultProps = { configPath: '', diff --git a/web/components/config/form-toggleswitch.tsx b/web/components/config/ToggleSwitch.tsx similarity index 88% rename from web/components/config/form-toggleswitch.tsx rename to web/components/config/ToggleSwitch.tsx index f162d0a57..943e5075d 100644 --- a/web/components/config/form-toggleswitch.tsx +++ b/web/components/config/ToggleSwitch.tsx @@ -2,7 +2,7 @@ // This one is styled to match the form-textfield component. // If `useSubmit` is true then it will automatically post to the config API onChange. -import React, { useState, useContext } from 'react'; +import React, { useState, useContext, FC } from 'react'; import { Switch } from 'antd'; import { createInputStatus, @@ -11,13 +11,12 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants'; - import { ServerStatusContext } from '../../utils/server-status-context'; -interface ToggleSwitchProps { +export type ToggleSwitchProps = { fieldName: string; apiPath?: string; @@ -29,8 +28,20 @@ interface ToggleSwitchProps { tip?: string; useSubmit?: boolean; onChange?: (arg: boolean) => void; -} -export default function ToggleSwitch(props: ToggleSwitchProps) { +}; + +export const ToggleSwitch: FC = ({ + apiPath, + checked, + reversed = false, + configPath = '', + disabled = false, + fieldName, + label, + tip, + useSubmit, + onChange, +}) => { const [submitStatus, setSubmitStatus] = useState(null); let resetTimer = null; @@ -38,19 +49,6 @@ export default function ToggleSwitch(props: ToggleSwitchProps) { const serverStatusData = useContext(ServerStatusContext); const { setFieldInConfigState } = serverStatusData || {}; - const { - apiPath, - checked, - reversed = false, - configPath = '', - disabled = false, - fieldName, - label, - tip, - useSubmit, - onChange, - } = props; - const resetStates = () => { setSubmitStatus(null); clearTimeout(resetTimer); @@ -107,7 +105,8 @@ export default function ToggleSwitch(props: ToggleSwitchProps) { ); -} +}; +export default ToggleSwitch; ToggleSwitch.defaultProps = { apiPath: '', diff --git a/web/components/config/video-latency.tsx b/web/components/config/VideoLatency.tsx similarity index 95% rename from web/components/config/video-latency.tsx rename to web/components/config/VideoLatency.tsx index 250b40255..4bbe3fa52 100644 --- a/web/components/config/video-latency.tsx +++ b/web/components/config/VideoLatency.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from 'react'; +import React, { useContext, useState, useEffect, FC } from 'react'; import { Typography, Slider } from 'antd'; import { ServerStatusContext } from '../../utils/server-status-context'; import { AlertMessageContext } from '../../utils/alert-message-context'; @@ -14,7 +14,7 @@ import { STATUS_PROCESSING, STATUS_SUCCESS, } from '../../utils/input-statuses'; -import FormStatusIndicator from './form-status-indicator'; +import { FormStatusIndicator } from './FormStatusIndicator'; const { Title } = Typography; @@ -34,7 +34,7 @@ const SLIDER_COMMENTS = { 4: 'Highest latency, highest error tolerance', }; -export default function VideoLatency() { +export const VideoLatency: FC = () => { const [submitStatus, setSubmitStatus] = useState(null); const [selectedOption, setSelectedOption] = useState(null); @@ -130,4 +130,4 @@ export default function VideoLatency() { ); -} +}; diff --git a/web/components/config/video-variant-form.tsx b/web/components/config/VideoVariantForm.tsx similarity index 98% rename from web/components/config/video-variant-form.tsx rename to web/components/config/VideoVariantForm.tsx index 4c14991b7..4e46fd5c8 100644 --- a/web/components/config/video-variant-form.tsx +++ b/web/components/config/VideoVariantForm.tsx @@ -1,10 +1,10 @@ // This content populates the video variant modal, which is spawned from the variants table. This relies on the `dataState` prop fed in by the table. -import React from 'react'; +import React, { FC } from 'react'; import { Popconfirm, Row, Col, Slider, Collapse, Typography } from 'antd'; import { ExclamationCircleFilled } from '@ant-design/icons'; import classNames from 'classnames'; import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section'; -import TextField from './form-textfield'; +import { TextField } from './TextField'; import { DEFAULT_VARIANT_STATE, VIDEO_VARIANT_SETTING_DEFAULTS, @@ -17,19 +17,19 @@ import { FRAMERATE_DEFAULTS, FRAMERATE_TOOLTIPS, } from '../../utils/config-constants'; -import ToggleSwitch from './form-toggleswitch'; +import { ToggleSwitch } from './ToggleSwitch'; const { Panel } = Collapse; -interface VideoVariantFormProps { +export type VideoVariantFormProps = { dataState: VideoVariant; onUpdateField: FieldUpdaterFunc; -} +}; -export default function VideoVariantForm({ +export const VideoVariantForm: FC = ({ dataState = DEFAULT_VARIANT_STATE, onUpdateField, -}: VideoVariantFormProps) { +}) => { const videoPassthroughEnabled = dataState.videoPassthrough; const handleFramerateChange = (value: number) => { @@ -314,4 +314,4 @@ export default function VideoVariantForm({ ); -} +}; diff --git a/web/components/config/notification/browser.tsx b/web/components/config/notification/browser.tsx index c84229dd1..7276e855d 100644 --- a/web/components/config/notification/browser.tsx +++ b/web/components/config/notification/browser.tsx @@ -1,13 +1,13 @@ import { Button, Typography } from 'antd'; import React, { useState, useContext, useEffect } from 'react'; import { ServerStatusContext } from '../../../utils/server-status-context'; -import TextField, { TEXTFIELD_TYPE_TEXTAREA } from '../form-textfield'; +import { TextField, TEXTFIELD_TYPE_TEXTAREA } from '../TextField'; import { postConfigUpdateToAPI, RESET_TIMEOUT, BROWSER_PUSH_CONFIG_FIELDS, } from '../../../utils/config-constants'; -import ToggleSwitch from '../form-toggleswitch'; +import { ToggleSwitch } from '../ToggleSwitch'; import { createInputStatus, StatusState, @@ -15,11 +15,11 @@ import { STATUS_SUCCESS, } from '../../../utils/input-statuses'; import { UpdateArgs } from '../../../types/config-section'; -import FormStatusIndicator from '../form-status-indicator'; +import { FormStatusIndicator } from '../FormStatusIndicator'; const { Title } = Typography; -export default function ConfigNotify() { +export const ConfigNotify = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { notifications } = serverConfig || {}; @@ -126,4 +126,5 @@ export default function ConfigNotify() { ); -} +}; +export default ConfigNotify; diff --git a/web/components/config/notification/discord.tsx b/web/components/config/notification/discord.tsx index e62f4dd3c..30e8f16ad 100644 --- a/web/components/config/notification/discord.tsx +++ b/web/components/config/notification/discord.tsx @@ -1,14 +1,14 @@ import { Button, Typography } from 'antd'; import React, { useState, useContext, useEffect } from 'react'; import { ServerStatusContext } from '../../../utils/server-status-context'; -import TextField from '../form-textfield'; -import FormStatusIndicator from '../form-status-indicator'; +import { TextField } from '../TextField'; +import { FormStatusIndicator } from '../FormStatusIndicator'; import { postConfigUpdateToAPI, RESET_TIMEOUT, DISCORD_CONFIG_FIELDS, } from '../../../utils/config-constants'; -import ToggleSwitch from '../form-toggleswitch'; +import { ToggleSwitch } from '../ToggleSwitch'; import { createInputStatus, StatusState, @@ -19,7 +19,7 @@ import { UpdateArgs } from '../../../types/config-section'; const { Title } = Typography; -export default function ConfigNotify() { +export const ConfigNotify = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { notifications } = serverConfig || {}; @@ -150,4 +150,5 @@ export default function ConfigNotify() { ); -} +}; +export default ConfigNotify; diff --git a/web/components/config/notification/federation.tsx b/web/components/config/notification/federation.tsx index 292c27de4..29cbcf7dc 100644 --- a/web/components/config/notification/federation.tsx +++ b/web/components/config/notification/federation.tsx @@ -5,7 +5,7 @@ import { ServerStatusContext } from '../../../utils/server-status-context'; const { Title } = Typography; -export default function ConfigNotify() { +export const ConfigNotify = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig } = serverStatusData || {}; const { federation } = serverConfig || {}; @@ -48,4 +48,5 @@ export default function ConfigNotify() { ); -} +}; +export default ConfigNotify; diff --git a/web/components/config/notification/twitter.tsx b/web/components/config/notification/twitter.tsx index 590781722..82e44dcae 100644 --- a/web/components/config/notification/twitter.tsx +++ b/web/components/config/notification/twitter.tsx @@ -1,14 +1,14 @@ import { Button, Typography } from 'antd'; import React, { useState, useContext, useEffect } from 'react'; import { ServerStatusContext } from '../../../utils/server-status-context'; -import TextField, { TEXTFIELD_TYPE_PASSWORD } from '../form-textfield'; -import FormStatusIndicator from '../form-status-indicator'; +import { TextField, TEXTFIELD_TYPE_PASSWORD } from '../TextField'; +import { FormStatusIndicator } from '../FormStatusIndicator'; import { postConfigUpdateToAPI, RESET_TIMEOUT, TWITTER_CONFIG_FIELDS, } from '../../../utils/config-constants'; -import ToggleSwitch from '../form-toggleswitch'; +import { ToggleSwitch } from '../ToggleSwitch'; import { createInputStatus, StatusState, @@ -16,11 +16,11 @@ import { STATUS_SUCCESS, } from '../../../utils/input-statuses'; import { UpdateArgs } from '../../../types/config-section'; -import { TEXTFIELD_TYPE_TEXT } from '../form-textfield-with-submit'; +import { TEXTFIELD_TYPE_TEXT } from '../TextFieldWithSubmit'; const { Title } = Typography; -export default function ConfigNotify() { +export const ConfigNotify = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { notifications } = serverConfig || {}; @@ -222,4 +222,5 @@ export default function ConfigNotify() { ); -} +}; +export default ConfigNotify; diff --git a/web/components/layouts/AdminLayout.tsx b/web/components/layouts/AdminLayout.tsx new file mode 100644 index 000000000..4df70a6a4 --- /dev/null +++ b/web/components/layouts/AdminLayout.tsx @@ -0,0 +1,15 @@ +import { AppProps } from 'next/app'; +import { FC } from 'react'; +import ServerStatusProvider from '../../utils/server-status-context'; +import AlertMessageProvider from '../../utils/alert-message-context'; +import { MainLayout } from '../MainLayout'; + +export const AdminLayout: FC = ({ Component, pageProps }) => ( + + + + + + + +); diff --git a/web/components/layouts/Main.tsx b/web/components/layouts/Main.tsx index b58a35996..8d7d068b6 100644 --- a/web/components/layouts/Main.tsx +++ b/web/components/layouts/Main.tsx @@ -1,20 +1,21 @@ import { Layout } from 'antd'; import { useRecoilValue } from 'recoil'; import Head from 'next/head'; -import { useEffect, useRef } from 'react'; +import { FC, useEffect, useRef } from 'react'; import { ClientConfigStore, isChatAvailableSelector, clientConfigStateAtom, fatalErrorStateAtom, } from '../stores/ClientConfigStore'; -import { Content, Header } from '../ui'; +import { Content } from '../ui/Content/Content'; +import { Header } from '../ui/Header/Header'; import { ClientConfig } from '../../interfaces/client-config.model'; import { DisplayableError } from '../../types/displayable-error'; -import FatalErrorStateModal from '../modals/FatalErrorStateModal/FatalErrorStateModal'; +import { FatalErrorStateModal } from '../modals/FatalErrorStateModal/FatalErrorStateModal'; import setupNoLinkReferrer from '../../utils/no-link-referrer'; -function Main() { +export const Main: FC = () => { const clientConfig = useRecoilValue(clientConfigStateAtom); const { name, title, customStyles } = clientConfig; const isChatAvailable = useRecoilValue(isChatAvailableSelector); @@ -97,6 +98,4 @@ function Main() { ); -} - -export default Main; +}; diff --git a/web/components/layouts/SimpleLayout.tsx b/web/components/layouts/SimpleLayout.tsx index eafa3d947..615ce04d5 100644 --- a/web/components/layouts/SimpleLayout.tsx +++ b/web/components/layouts/SimpleLayout.tsx @@ -1,11 +1,8 @@ import { AppProps } from 'next/app'; +import { FC } from 'react'; -function SimpleLayout({ Component, pageProps }: AppProps) { - return ( -
- -
- ); -} - -export default SimpleLayout; +export const SimpleLayout: FC = ({ Component, pageProps }) => ( +
+ +
+); diff --git a/web/components/layouts/admin-layout.tsx b/web/components/layouts/admin-layout.tsx deleted file mode 100644 index d06dc2188..000000000 --- a/web/components/layouts/admin-layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { AppProps } from 'next/app'; -import ServerStatusProvider from '../../utils/server-status-context'; -import AlertMessageProvider from '../../utils/alert-message-context'; -import MainLayout from '../main-layout'; - -function AdminLayout({ Component, pageProps }: AppProps) { - return ( - - - - - - - - ); -} - -export default AdminLayout; diff --git a/web/components/modals/AuthModal/AuthModal.stories.tsx b/web/components/modals/AuthModal/AuthModal.stories.tsx index 57dcf5d67..a922525b9 100644 --- a/web/components/modals/AuthModal/AuthModal.stories.tsx +++ b/web/components/modals/AuthModal/AuthModal.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import AuthModal from './AuthModal'; +import { AuthModal } from './AuthModal'; const Example = () => (
diff --git a/web/components/modals/AuthModal/AuthModal.tsx b/web/components/modals/AuthModal/AuthModal.tsx index f883a14f4..41a3ba6d5 100644 --- a/web/components/modals/AuthModal/AuthModal.tsx +++ b/web/components/modals/AuthModal/AuthModal.tsx @@ -1,12 +1,13 @@ import { Tabs } from 'antd'; import { useRecoilValue } from 'recoil'; -import IndieAuthModal from '../IndieAuthModal/IndieAuthModal'; -import FediAuthModal from '../FediAuthModal/FediAuthModal'; +import { FC } from 'react'; +import { IndieAuthModal } from '../IndieAuthModal/IndieAuthModal'; +import { FediAuthModal } from '../FediAuthModal/FediAuthModal'; import FediverseIcon from '../../../assets/images/fediverse-black.png'; import IndieAuthIcon from '../../../assets/images/indieauth.png'; -import s from './AuthModal.module.scss'; +import styles from './AuthModal.module.scss'; import { chatDisplayNameAtom, chatAuthenticatedAtom, @@ -15,10 +16,7 @@ import { const { TabPane } = Tabs; -/* eslint-disable @typescript-eslint/no-unused-vars */ -interface Props {} - -export default function AuthModal(props: Props) { +export const AuthModal: FC = () => { const chatDisplayName = useRecoilValue(chatDisplayNameAtom); const authenticated = useRecoilValue(chatAuthenticatedAtom); const accessToken = useRecoilValue(accessTokenAtom); @@ -34,8 +32,8 @@ export default function AuthModal(props: Props) { > - IndieAuth + + IndieAuth IndieAuth } @@ -49,8 +47,8 @@ export default function AuthModal(props: Props) { - Fediverse auth + + Fediverse auth FediAuth } @@ -61,4 +59,4 @@ export default function AuthModal(props: Props) {
); -} +}; diff --git a/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.stories.tsx b/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.stories.tsx index 4826f6475..1da8f0bb2 100644 --- a/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.stories.tsx +++ b/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import BrowserNotifyModal from './BrowserNotifyModal'; +import { BrowserNotifyModal } from './BrowserNotifyModal'; import BrowserNotifyModalMock from '../../../stories/assets/mocks/notify-modal.png'; const Example = () => ( diff --git a/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.tsx b/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.tsx index 6299c0081..4673bdcb8 100644 --- a/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.tsx +++ b/web/components/modals/BrowserNotifyModal/BrowserNotifyModal.tsx @@ -1,69 +1,64 @@ import { Row, Col, Spin, Typography, Button } from 'antd'; -import React, { useState } from 'react'; +import React, { FC, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { accessTokenAtom, clientConfigStateAtom } from '../../stores/ClientConfigStore'; import { registerWebPushNotifications, saveNotificationRegistration, } from '../../../services/notifications-service'; -import s from './BrowserNotifyModal.module.scss'; +import styles from './BrowserNotifyModal.module.scss'; import isPushNotificationSupported from '../../../utils/browserPushNotifications'; const { Title } = Typography; -function NotificationsNotSupported() { - return
Browser notifications are not supported in your browser.
; -} +const NotificationsNotSupported = () => ( +
Browser notifications are not supported in your browser.
+); -function NotificationsEnabled() { - return
Notifications enabled
; -} +const NotificationsEnabled = () =>
Notifications enabled
; -interface PermissionPopupPreviewProps { +export type PermissionPopupPreviewProps = { start: () => void; -} -function PermissionPopupPreview(props: PermissionPopupPreviewProps) { - const { start } = props; +}; - return ( -
-
-
{window.location.toString()} wants to
-
- - - - Show notifications -
-
- - -
+const PermissionPopupPreview: FC = ({ start }) => ( +
+
+
{window.location.toString()} wants to
+
+ + + + Show notifications +
+
+ +
- ); -} +
+); -export default function BrowserNotifyModal() { +export const BrowserNotifyModal = () => { const [error, setError] = useState(null); const accessToken = useRecoilValue(accessTokenAtom); const config = useRecoilValue(clientConfigStateAtom); @@ -120,4 +115,4 @@ export default function BrowserNotifyModal() { ); -} +}; diff --git a/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.stories.tsx b/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.stories.tsx index 44977e7bb..c68817e56 100644 --- a/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.stories.tsx +++ b/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import FatalErrorStateModal from './FatalErrorStateModal'; +import { FatalErrorStateModal } from './FatalErrorStateModal'; export default { title: 'owncast/Modals/Global error state', diff --git a/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.tsx b/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.tsx index 7ea1c981f..f396e8178 100644 --- a/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.tsx +++ b/web/components/modals/FatalErrorStateModal/FatalErrorStateModal.tsx @@ -1,25 +1,22 @@ import { Modal } from 'antd'; +import { FC } from 'react'; -interface Props { +export type FatalErrorStateModalProps = { title: string; message: string; -} +}; -export default function FatalErrorStateModal(props: Props) { - const { title, message } = props; - - return ( - -

{message}

-
- ); -} +export const FatalErrorStateModal: FC = ({ title, message }) => ( + +

{message}

+
+); diff --git a/web/components/modals/FediAuthModal/FediAuthModal.stories.tsx b/web/components/modals/FediAuthModal/FediAuthModal.stories.tsx index 2c62aca90..e642e081c 100644 --- a/web/components/modals/FediAuthModal/FediAuthModal.stories.tsx +++ b/web/components/modals/FediAuthModal/FediAuthModal.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import FediAuthModal from './FediAuthModal'; +import { FediAuthModal } from './FediAuthModal'; import FediAuthModalMock from '../../../stories/assets/mocks/fediauth-modal.png'; const Example = () => ( diff --git a/web/components/modals/FediAuthModal/FediAuthModal.tsx b/web/components/modals/FediAuthModal/FediAuthModal.tsx index cac51f62f..d055b56a6 100644 --- a/web/components/modals/FediAuthModal/FediAuthModal.tsx +++ b/web/components/modals/FediAuthModal/FediAuthModal.tsx @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -interface Props {} +import { FC } from 'react'; -export default function FediAuthModal(props: Props) { - return
Component goes here
; -} +export const FediAuthModal: FC = () =>
Component goes here
; diff --git a/web/components/modals/FollowModal/FollowModal.stories.tsx b/web/components/modals/FollowModal/FollowModal.stories.tsx index 63b3d3c8a..ee9716984 100644 --- a/web/components/modals/FollowModal/FollowModal.stories.tsx +++ b/web/components/modals/FollowModal/FollowModal.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import FollowModal from './FollowModal'; +import { FollowModal } from './FollowModal'; import FollowModalMock from '../../../stories/assets/mocks/follow-modal.png'; const Example = () => ( diff --git a/web/components/modals/FollowModal/FollowModal.tsx b/web/components/modals/FollowModal/FollowModal.tsx index e32222436..ca589447a 100644 --- a/web/components/modals/FollowModal/FollowModal.tsx +++ b/web/components/modals/FollowModal/FollowModal.tsx @@ -1,15 +1,15 @@ /* eslint-disable react/no-unescaped-entities */ import { Input, Button, Alert, Spin, Space } from 'antd'; -import { useState } from 'react'; -import s from './FollowModal.module.scss'; +import { FC, useState } from 'react'; +import styles from './FollowModal.module.scss'; const ENDPOINT = '/api/remotefollow'; -interface Props { +export type FollowModalProps = { handleClose: () => void; account: string; name: string; -} +}; function validateAccount(a) { const sanitized = a.replace(/^@+/, ''); @@ -18,8 +18,7 @@ function validateAccount(a) { return regex.test(String(sanitized).toLowerCase()); } -export default function FollowModal(props: Props) { - const { handleClose, account, name } = props; +export const FollowModal: FC = ({ handleClose, account, name }) => { const [remoteAccount, setRemoteAccount] = useState(null); const [valid, setValid] = useState(false); const [loading, setLoading] = useState(false); @@ -76,7 +75,7 @@ export default function FollowModal(props: Props) { return ( -
+
By following this stream you'll get notified on the Fediverse when it goes live. Now is a great time to @@ -89,16 +88,16 @@ export default function FollowModal(props: Props) { {errorMessage && ( )} -
- logo -
-
{name}
+
+ logo +
+
{name}
{account}
-
Enter your username @server to follow
+
Enter your username @server to follow
-
+
You'll be redirected to your Fediverse server and asked to confirm the action.
- + @@ -121,4 +120,4 @@ export default function FollowModal(props: Props) { ); -} +}; diff --git a/web/components/modals/IndieAuthModal/IndieAuthModal.stories.tsx b/web/components/modals/IndieAuthModal/IndieAuthModal.stories.tsx index 3d77260d9..9a5a88594 100644 --- a/web/components/modals/IndieAuthModal/IndieAuthModal.stories.tsx +++ b/web/components/modals/IndieAuthModal/IndieAuthModal.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import IndieAuthModal from './IndieAuthModal'; +import { IndieAuthModal } from './IndieAuthModal'; import Mock from '../../../stories/assets/mocks/indieauth-modal.png'; const Example = () => ( diff --git a/web/components/modals/IndieAuthModal/IndieAuthModal.tsx b/web/components/modals/IndieAuthModal/IndieAuthModal.tsx index 6f0919f83..909447a77 100644 --- a/web/components/modals/IndieAuthModal/IndieAuthModal.tsx +++ b/web/components/modals/IndieAuthModal/IndieAuthModal.tsx @@ -1,19 +1,21 @@ import { Alert, Button, Input, Space, Spin, Collapse, Typography } from 'antd'; -import React, { useState } from 'react'; +import React, { FC, useState } from 'react'; import isValidURL from '../../../utils/urls'; const { Panel } = Collapse; const { Link } = Typography; -interface Props { +export type IndieAuthModalProps = { authenticated: boolean; displayName: string; accessToken: string; -} - -export default function IndieAuthModal(props: Props) { - const { authenticated, displayName: username, accessToken } = props; +}; +export const IndieAuthModal: FC = ({ + authenticated, + displayName: username, + accessToken, +}) => { const [errorMessage, setErrorMessage] = useState(null); const [loading, setLoading] = useState(false); const [valid, setValid] = useState(false); @@ -153,4 +155,4 @@ export default function IndieAuthModal(props: Props) { ); -} +}; diff --git a/web/components/modals/NameChangeModal/NameChangeModal.stories.tsx b/web/components/modals/NameChangeModal/NameChangeModal.stories.tsx index bbc9549ea..0e34f9b64 100644 --- a/web/components/modals/NameChangeModal/NameChangeModal.stories.tsx +++ b/web/components/modals/NameChangeModal/NameChangeModal.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import NameChangeModal from './NameChangeModal'; +import { NameChangeModal } from './NameChangeModal'; export default { title: 'owncast/Modals/Name change', diff --git a/web/components/modals/NameChangeModal/NameChangeModal.tsx b/web/components/modals/NameChangeModal/NameChangeModal.tsx index 9146bfb90..5cc363c10 100644 --- a/web/components/modals/NameChangeModal/NameChangeModal.tsx +++ b/web/components/modals/NameChangeModal/NameChangeModal.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties, useState } from 'react'; +import React, { CSSProperties, FC, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { Input, Button, Select } from 'antd'; import { MessageType } from '../../../interfaces/socket-events'; @@ -11,11 +11,11 @@ import { const { Option } = Select; -/* eslint-disable @typescript-eslint/no-unused-vars */ -interface Props {} +export type UserColorProps = { + color: number; +}; -function UserColor(props: { color: number }): React.ReactElement { - const { color } = props; +const UserColor: FC = ({ color }) => { const style: CSSProperties = { textAlign: 'center', backgroundColor: `var(--theme-color-users-${color})`, @@ -23,9 +23,9 @@ function UserColor(props: { color: number }): React.ReactElement { height: '100%', }; return
; -} +}; -export default function NameChangeModal(props: Props) { +export const NameChangeModal: FC = () => { const websocketService = useRecoilValue(websocketServiceAtom); const chatDisplayName = useRecoilValue(chatDisplayNameAtom); const chatDisplayColor = useRecoilValue(chatDisplayColorAtom) || 0; @@ -85,4 +85,4 @@ export default function NameChangeModal(props: Props) {
); -} +}; diff --git a/web/components/stores/ClientConfigStore.tsx b/web/components/stores/ClientConfigStore.tsx index 6d777c251..130e30fbf 100644 --- a/web/components/stores/ClientConfigStore.tsx +++ b/web/components/stores/ClientConfigStore.tsx @@ -170,7 +170,7 @@ function mergeMeta(meta) { }, {}); } -export function ClientConfigStore() { +export const ClientConfigStore = () => { const [appState, appStateSend, appStateService] = useMachine(appStateModel); const setChatDisplayName = useSetRecoilState(chatDisplayNameAtom); @@ -377,4 +377,4 @@ export function ClientConfigStore() { }); return null; -} +}; diff --git a/web/components/stores/eventhandlers/connected-client-info-handler.ts b/web/components/stores/eventhandlers/connected-client-info-handler.ts index b10c13779..3941b5e25 100644 --- a/web/components/stores/eventhandlers/connected-client-info-handler.ts +++ b/web/components/stores/eventhandlers/connected-client-info-handler.ts @@ -1,6 +1,6 @@ import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events'; -export default function handleConnectedClientInfoMessage( +export function handleConnectedClientInfoMessage( message: ConnectedClientInfoEvent, setChatDisplayName: (string) => void, setChatDisplayColor: (number) => void, @@ -16,3 +16,4 @@ export default function handleConnectedClientInfoMessage( setIsChatModerator(scopes?.includes('MODERATOR')); setChatAuthenticated(authenticated); } +export default handleConnectedClientInfoMessage; diff --git a/web/components/stores/eventhandlers/handleNameChangeEvent.tsx b/web/components/stores/eventhandlers/handleNameChangeEvent.tsx index f02c11828..1889485c2 100644 --- a/web/components/stores/eventhandlers/handleNameChangeEvent.tsx +++ b/web/components/stores/eventhandlers/handleNameChangeEvent.tsx @@ -1,7 +1,7 @@ import { ChatMessage } from '../../../interfaces/chat-message.model'; import { ChatEvent } from '../../../interfaces/socket-events'; -export default function handleNameChangeEvent( +export function handleNameChangeEvent( message: ChatEvent, messages: ChatMessage[], setChatMessages, @@ -9,3 +9,4 @@ export default function handleNameChangeEvent( const updatedMessages = [...messages, message]; setChatMessages(updatedMessages); } +export default handleNameChangeEvent; diff --git a/web/components/ui/Content/Content.tsx b/web/components/ui/Content/Content.tsx index 84f400972..151d5bfca 100644 --- a/web/components/ui/Content/Content.tsx +++ b/web/components/ui/Content/Content.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/no-danger */ import { useRecoilState, useRecoilValue } from 'recoil'; import { Layout, Tabs, Spin } from 'antd'; -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import cn from 'classnames'; import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage'; @@ -17,32 +17,32 @@ import { serverStatusState, } from '../../stores/ClientConfigStore'; import { ClientConfig } from '../../../interfaces/client-config.model'; -import CustomPageContent from '../CustomPageContent/CustomPageContent'; -import OwncastPlayer from '../../video/OwncastPlayer/OwncastPlayer'; -import FollowerCollection from '../followers/FollowerCollection/FollowerCollection'; -import s from './Content.module.scss'; -import Sidebar from '../Sidebar'; -import Footer from '../Footer'; +import { CustomPageContent } from '../CustomPageContent/CustomPageContent'; +import { OwncastPlayer } from '../../video/OwncastPlayer/OwncastPlayer'; +import { FollowerCollection } from '../followers/FollowerCollection/FollowerCollection'; +import styles from './Content.module.scss'; +import { Sidebar } from '../Sidebar/Sidebar'; +import { Footer } from '../Footer/Footer'; // import ChatContainer from '../../chat/ChatContainer'; // import { ChatMessage } from '../../../interfaces/chat-message.model'; // import ChatTextField from '../../chat/ChatTextField/ChatTextField'; -import ActionButtonRow from '../../action-buttons/ActionButtonRow/ActionButtonRow'; -import ActionButton from '../../action-buttons/ActionButton/ActionButton'; -import NotifyReminderPopup from '../NotifyReminderPopup/NotifyReminderPopup'; -import OfflineBanner from '../OfflineBanner/OfflineBanner'; +import { ActionButtonRow } from '../../action-buttons/ActionButtonRow/ActionButtonRow'; +import { ActionButton } from '../../action-buttons/ActionButton/ActionButton'; +import { NotifyReminderPopup } from '../NotifyReminderPopup/NotifyReminderPopup'; +import { OfflineBanner } from '../OfflineBanner/OfflineBanner'; import { AppStateOptions } from '../../stores/application-state'; -import FollowButton from '../../action-buttons/FollowButton'; -import NotifyButton from '../../action-buttons/NotifyButton'; -import Modal from '../Modal/Modal'; -import BrowserNotifyModal from '../../modals/BrowserNotifyModal/BrowserNotifyModal'; -import ContentHeader from '../../common/ContentHeader'; +import { FollowButton } from '../../action-buttons/FollowButton'; +import { NotifyButton } from '../../action-buttons/NotifyButton'; +import { Modal } from '../Modal/Modal'; +import { BrowserNotifyModal } from '../../modals/BrowserNotifyModal/BrowserNotifyModal'; +import { ContentHeader } from '../../common/ContentHeader/ContentHeader'; import { ServerStatus } from '../../../interfaces/server-status.model'; -import { StatusBar } from '..'; +import { Statusbar } from '../Statusbar/Statusbar'; const { TabPane } = Tabs; -const { Content } = Layout; +const { Content: AntContent } = Layout; -export default function ContentComponent() { +export const Content: FC = () => { const appState = useRecoilValue(appStateAtom); const clientConfig = useRecoilValue(clientConfigStateAtom); const isChatVisible = useRecoilValue(isChatVisibleSelector); @@ -105,17 +105,17 @@ export default function ContentComponent() { window.addEventListener('resize', checkIfMobile); }, []); - const rootClassName = cn(s.root, { - [s.mobile]: isMobile, + const rootClassName = cn(styles.root, { + [styles.mobile]: isMobile, }); return (
- -
- + +
+ -
+
{online && } {!online && ( )} -
-
-
+
+
{externalActionButtons} @@ -156,7 +156,7 @@ export default function ContentComponent() {
-
+
-
+
{isChatVisible && isMobile && ( @@ -184,18 +184,19 @@ export default function ContentComponent() {
*/} )} - + - +
{isChatVisible && !isMobile && } - + {!isMobile &&
}
); -} +}; +export default Content; diff --git a/web/components/ui/Content/index.ts b/web/components/ui/Content/index.ts deleted file mode 100644 index b8403143a..000000000 --- a/web/components/ui/Content/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Content'; diff --git a/web/components/ui/CrossfadeImage/CrossfadeImage.tsx b/web/components/ui/CrossfadeImage/CrossfadeImage.tsx index 88ce9e891..dc1c1ad1c 100644 --- a/web/components/ui/CrossfadeImage/CrossfadeImage.tsx +++ b/web/components/ui/CrossfadeImage/CrossfadeImage.tsx @@ -1,14 +1,14 @@ -import React, { useMemo, useState } from 'react'; +import React, { FC, useMemo, useState } from 'react'; type ObjectFit = React.CSSProperties['objectFit']; -interface CrossfadeImageProps { +export type CrossfadeImageProps = { src: string; width: string; height: string; objectFit?: ObjectFit; duration?: string; -} +}; const imgStyle: React.CSSProperties = { position: 'absolute', @@ -16,13 +16,13 @@ const imgStyle: React.CSSProperties = { height: `100%`, }; -export default function CrossfadeImage({ +export const CrossfadeImage: FC = ({ src = '', width, height, objectFit = 'fill', duration = '1s', -}: CrossfadeImageProps) { +}) => { const spanStyle: React.CSSProperties = useMemo( () => ({ display: 'inline-block', @@ -67,7 +67,8 @@ export default function CrossfadeImage({ )} ); -} +}; +export default CrossfadeImage; CrossfadeImage.defaultProps = { objectFit: 'fill', diff --git a/web/components/ui/CustomPageContent/CustomPageContent.stories.tsx b/web/components/ui/CustomPageContent/CustomPageContent.stories.tsx index 6fccdf986..771abca64 100644 --- a/web/components/ui/CustomPageContent/CustomPageContent.stories.tsx +++ b/web/components/ui/CustomPageContent/CustomPageContent.stories.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-useless-escape */ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import CustomPageContent from './CustomPageContent'; +import { CustomPageContent } from './CustomPageContent'; export default { title: 'owncast/Components/Custom page content', diff --git a/web/components/ui/CustomPageContent/CustomPageContent.tsx b/web/components/ui/CustomPageContent/CustomPageContent.tsx index 3d9255a7f..c4f0f3b57 100644 --- a/web/components/ui/CustomPageContent/CustomPageContent.tsx +++ b/web/components/ui/CustomPageContent/CustomPageContent.tsx @@ -1,16 +1,13 @@ /* eslint-disable react/no-danger */ -import s from './CustomPageContent.module.scss'; +import { FC } from 'react'; +import styles from './CustomPageContent.module.scss'; -interface Props { +export type CustomPageContentProps = { content: string; -} +}; -export default function CustomPageContent(props: Props) { - const { content } = props; - // eslint-disable-next-line react/no-danger - return ( -
-
-
- ); -} +export const CustomPageContent: FC = ({ content }) => ( +
+
+
+); diff --git a/web/components/ui/Footer/Footer.stories.tsx b/web/components/ui/Footer/Footer.stories.tsx index fa2c1b8b4..69ee6cd6b 100644 --- a/web/components/ui/Footer/Footer.stories.tsx +++ b/web/components/ui/Footer/Footer.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import Footer from './Footer'; +import { Footer } from './Footer'; export default { title: 'owncast/Layout/Footer', diff --git a/web/components/ui/Footer/Footer.tsx b/web/components/ui/Footer/Footer.tsx index 01c08f613..5c25580e1 100644 --- a/web/components/ui/Footer/Footer.tsx +++ b/web/components/ui/Footer/Footer.tsx @@ -1,34 +1,32 @@ -import s from './Footer.module.scss'; +import { FC } from 'react'; +import styles from './Footer.module.scss'; -interface Props { +export type FooterProps = { version: string; -} +}; -export default function FooterComponent(props: Props) { - const { version } = props; - - return ( -
-
- Powered by {version} +export const Footer: FC = ({ version }) => ( +
+ + +); +export default Footer; diff --git a/web/components/ui/Footer/index.ts b/web/components/ui/Footer/index.ts deleted file mode 100644 index be92134c1..000000000 --- a/web/components/ui/Footer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Footer'; diff --git a/web/components/ui/Header/Header.stories.tsx b/web/components/ui/Header/Header.stories.tsx index 3f912ad98..a98674988 100644 --- a/web/components/ui/Header/Header.stories.tsx +++ b/web/components/ui/Header/Header.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import Header from './Header'; +import { Header } from './Header'; export default { title: 'owncast/Layout/Header', diff --git a/web/components/ui/Header/Header.tsx b/web/components/ui/Header/Header.tsx index 14068026d..72aed7e1a 100644 --- a/web/components/ui/Header/Header.tsx +++ b/web/components/ui/Header/Header.tsx @@ -1,29 +1,30 @@ import { Layout, Tag, Tooltip } from 'antd'; -import { OwncastLogo, UserDropdown } from '../../common'; -import s from './Header.module.scss'; +import { FC } from 'react'; +import { UserDropdown } from '../../common/UserDropdown/UserDropdown'; +import { OwncastLogo } from '../../common/OwncastLogo/OwncastLogo'; +import styles from './Header.module.scss'; -const { Header } = Layout; +const { Header: AntHeader } = Layout; -interface Props { +export type HeaderComponentProps = { name: string; chatAvailable: boolean; -} +}; -export default function HeaderComponent({ name = 'Your stream title', chatAvailable }: Props) { - return ( -
-
- - {name} -
- {chatAvailable && } - {!chatAvailable && ( - - - Chat offline - - - )} -
- ); -} +export const Header: FC = ({ name = 'Your stream title', chatAvailable }) => ( + +
+ + {name} +
+ {chatAvailable && } + {!chatAvailable && ( + + + Chat offline + + + )} +
+); +export default Header; diff --git a/web/components/ui/Header/index.ts b/web/components/ui/Header/index.ts deleted file mode 100644 index 579f1ac23..000000000 --- a/web/components/ui/Header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Header'; diff --git a/web/components/ui/Logo/Logo.tsx b/web/components/ui/Logo/Logo.tsx index 1949c13e1..5b590145d 100644 --- a/web/components/ui/Logo/Logo.tsx +++ b/web/components/ui/Logo/Logo.tsx @@ -1,16 +1,16 @@ import { Image } from 'antd'; -import s from './Logo.module.scss'; +import { FC } from 'react'; +import styles from './Logo.module.scss'; -interface Props { +export type LogoProps = { src: string; -} +}; -export default function Logo({ src }: Props) { - return ( -
-
- -
+export const Logo: FC = ({ src }) => ( +
+
+
- ); -} +
+); +export default Logo; diff --git a/web/components/ui/Logo/index.ts b/web/components/ui/Logo/index.ts deleted file mode 100644 index a5be7785e..000000000 --- a/web/components/ui/Logo/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Logo'; diff --git a/web/components/ui/ModIcon.tsx b/web/components/ui/ModIcon.tsx index 9e76454b6..c4b97be7f 100644 --- a/web/components/ui/ModIcon.tsx +++ b/web/components/ui/ModIcon.tsx @@ -1,31 +1,29 @@ -/* eslint-disable react/require-default-props */ -import { CSSProperties } from 'react'; +import { CSSProperties, FC } from 'react'; -interface Props { +export type ModIconProps = { style?: CSSProperties; fill?: string; stroke?: string; -} -export default function ModIcon({ +}; + +export const ModIcon: FC = ({ style = { width: '1rem', height: '1rem' }, fill = 'none', stroke = 'var(--color-owncast-gray-300)', -}: Props) { - return ( - - This user has moderation rights - - - ); -} +}: ModIconProps) => ( + + This user has moderation rights + + +); diff --git a/web/components/ui/Modal/Modal.stories.tsx b/web/components/ui/Modal/Modal.stories.tsx index 88f42ae73..cacf58317 100644 --- a/web/components/ui/Modal/Modal.stories.tsx +++ b/web/components/ui/Modal/Modal.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import Modal from './Modal'; +import { Modal } from './Modal'; export default { title: 'owncast/Modals/Container', diff --git a/web/components/ui/Modal/Modal.tsx b/web/components/ui/Modal/Modal.tsx index 9010433fd..1b08ce70c 100644 --- a/web/components/ui/Modal/Modal.tsx +++ b/web/components/ui/Modal/Modal.tsx @@ -1,8 +1,8 @@ import { Spin, Skeleton, Modal as AntModal } from 'antd'; -import React, { ReactNode, useState } from 'react'; -import s from './Modal.module.scss'; +import React, { FC, ReactNode, useState } from 'react'; +import styles from './Modal.module.scss'; -interface Props { +export type ModalProps = { title: string; url?: string; visible: boolean; @@ -12,11 +12,19 @@ interface Props { children?: ReactNode; height?: string; width?: string; -} +}; -export default function Modal(props: Props) { - const { title, url, visible, handleOk, handleCancel, afterClose, height, width, children } = - props; +export const Modal: FC = ({ + title, + url, + visible, + handleOk, + handleCancel, + afterClose, + height, + width, + children, +}) => { const [loading, setLoading] = useState(!!url); const modalStyle = { @@ -60,12 +68,13 @@ export default function Modal(props: Props) { )} {iframe &&
{iframe}
} - {children &&
{children}
} - {loading && } + {children &&
{children}
} + {loading && } ); -} +}; +export default Modal; Modal.defaultProps = { url: undefined, diff --git a/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.stories.tsx b/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.stories.tsx index 89fe55782..e0bafa37f 100644 --- a/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.stories.tsx +++ b/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.stories.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-alert */ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import NotifyReminderPopup from './NotifyReminderPopup'; +import { NotifyReminderPopup } from './NotifyReminderPopup'; import Mock from '../../../stories/assets/mocks/notify-popup.png'; const Example = args => ( diff --git a/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx b/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx index dcea5eb55..3afc039d0 100644 --- a/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx +++ b/web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx @@ -1,17 +1,21 @@ import { Popover } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; -import React, { useState, useEffect } from 'react'; -import s from './NotifyReminderPopup.module.scss'; +import React, { useState, useEffect, FC } from 'react'; +import styles from './NotifyReminderPopup.module.scss'; -interface Props { +export type NotifyReminderPopupProps = { visible: boolean; children: React.ReactNode; notificationClicked: () => void; notificationClosed: () => void; -} +}; -export default function NotifyReminderPopup(props: Props) { - const { children, visible, notificationClicked, notificationClosed } = props; +export const NotifyReminderPopup: FC = ({ + children, + visible, + notificationClicked, + notificationClosed, +}) => { const [visiblePopup, setVisiblePopup] = useState(visible); const [mounted, setMounted] = useState(false); @@ -23,7 +27,7 @@ export default function NotifyReminderPopup(props: Props) { setMounted(true); }, []); - const title =
Stay updated!
; + const title =
Stay updated!
; const popupStyle = { borderRadius: '5px', cursor: 'pointer', @@ -45,10 +49,10 @@ export default function NotifyReminderPopup(props: Props) { const content = (
- -
+
Click and never miss
future streams! @@ -71,4 +75,4 @@ export default function NotifyReminderPopup(props: Props) { ) ); -} +}; diff --git a/web/components/ui/OfflineBanner/OfflineBanner.stories.tsx b/web/components/ui/OfflineBanner/OfflineBanner.stories.tsx index ab79017b8..70904eb83 100644 --- a/web/components/ui/OfflineBanner/OfflineBanner.stories.tsx +++ b/web/components/ui/OfflineBanner/OfflineBanner.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import OfflineBanner from './OfflineBanner'; +import { OfflineBanner } from './OfflineBanner'; import OfflineState from '../../../stories/assets/mocks/offline-state.png'; export default { diff --git a/web/components/ui/OfflineBanner/OfflineBanner.tsx b/web/components/ui/OfflineBanner/OfflineBanner.tsx index fcf6ad97c..eb2f2104f 100644 --- a/web/components/ui/OfflineBanner/OfflineBanner.tsx +++ b/web/components/ui/OfflineBanner/OfflineBanner.tsx @@ -1,32 +1,26 @@ import { Divider, Button } from 'antd'; import { NotificationFilled } from '@ant-design/icons'; +import { FC } from 'react'; +import styles from './OfflineBanner.module.scss'; -import s from './OfflineBanner.module.scss'; - -interface Props { +export type OfflineBannerProps = { name: string; text: string; -} +}; -export default function OfflineBanner({ name, text }: Props) { - const handleShowNotificationModal = () => { - console.log('show notification modal'); - }; +export const OfflineBanner: FC = ({ name, text }) => ( +
+
+
{name} is currently offline.
+ +
{text}
- return ( -
-
-
{name} is currently offline.
- -
{text}
- -
- -
+
+
- ); -} +
+); diff --git a/web/components/ui/Sidebar/Sidebar.tsx b/web/components/ui/Sidebar/Sidebar.tsx index e4f66307e..b09697545 100644 --- a/web/components/ui/Sidebar/Sidebar.tsx +++ b/web/components/ui/Sidebar/Sidebar.tsx @@ -1,8 +1,9 @@ import Sider from 'antd/lib/layout/Sider'; import { useRecoilValue } from 'recoil'; +import { FC } from 'react'; import { ChatMessage } from '../../../interfaces/chat-message.model'; -import { ChatContainer } from '../../chat'; -import s from './Sidebar.module.scss'; +import { ChatContainer } from '../../chat/ChatContainer/ChatContainer'; +import styles from './Sidebar.module.scss'; import { chatDisplayNameAtom, @@ -11,14 +12,14 @@ import { visibleChatMessagesSelector, } from '../../stores/ClientConfigStore'; -export default function Sidebar() { +export const Sidebar: FC = () => { const chatDisplayName = useRecoilValue(chatDisplayNameAtom); const chatUserId = useRecoilValue(chatUserIdAtom); const isChatModerator = useRecoilValue(isChatModeratorAtom); const messages = useRecoilValue(visibleChatMessagesSelector); return ( - + ); -} +}; diff --git a/web/components/ui/Sidebar/index.ts b/web/components/ui/Sidebar/index.ts deleted file mode 100644 index e842a8591..000000000 --- a/web/components/ui/Sidebar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Sidebar'; diff --git a/web/components/ui/SocialLinks/SocialLinks.stories.tsx b/web/components/ui/SocialLinks/SocialLinks.stories.tsx index ecc3f78c1..58e928196 100644 --- a/web/components/ui/SocialLinks/SocialLinks.stories.tsx +++ b/web/components/ui/SocialLinks/SocialLinks.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import SocialLinks from './SocialLinks'; +import { SocialLinks } from './SocialLinks'; export default { title: 'owncast/Components/Social links', diff --git a/web/components/ui/SocialLinks/SocialLinks.tsx b/web/components/ui/SocialLinks/SocialLinks.tsx index 77227a7b0..678743aa8 100644 --- a/web/components/ui/SocialLinks/SocialLinks.tsx +++ b/web/components/ui/SocialLinks/SocialLinks.tsx @@ -1,21 +1,24 @@ +import { FC } from 'react'; import { SocialLink } from '../../../interfaces/social-link.model'; -import s from './SocialLinks.module.scss'; +import styles from './SocialLinks.module.scss'; -interface Props { - // eslint-disable-next-line react/no-unused-prop-types +export type SocialLinksProps = { links: SocialLink[]; -} +}; // eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function SocialLinks(props: Props) { - const { links } = props; - return ( -
- {links.map(link => ( - - {link.platform} - - ))} -
- ); -} +export const SocialLinks: FC = ({ links }) => ( +
+ {links.map(link => ( + + {link.platform} + + ))} +
+); diff --git a/web/components/ui/Statusbar/StatusBar.stories.tsx b/web/components/ui/Statusbar/StatusBar.stories.tsx index 09fcd6e0d..633e88759 100644 --- a/web/components/ui/Statusbar/StatusBar.stories.tsx +++ b/web/components/ui/Statusbar/StatusBar.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { subHours } from 'date-fns'; -import Statusbar from './Statusbar'; +import { Statusbar } from './Statusbar'; export default { title: 'owncast/Player/Status bar', diff --git a/web/components/ui/Statusbar/Statusbar.tsx b/web/components/ui/Statusbar/Statusbar.tsx index a3e5103f6..0a1bd04b8 100644 --- a/web/components/ui/Statusbar/Statusbar.tsx +++ b/web/components/ui/Statusbar/Statusbar.tsx @@ -1,15 +1,15 @@ import formatDistanceToNow from 'date-fns/formatDistanceToNow'; import intervalToDuration from 'date-fns/intervalToDuration'; -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { EyeOutlined } from '@ant-design/icons'; -import s from './Statusbar.module.scss'; +import styles from './Statusbar.module.scss'; -interface Props { +export type StatusbarProps = { online: Boolean; lastConnectTime?: Date; lastDisconnectTime?: Date; viewerCount: number; -} +}; function makeDurationString(lastConnectTime: Date): string { const diff = intervalToDuration({ start: lastConnectTime, end: new Date() }); @@ -22,7 +22,13 @@ function makeDurationString(lastConnectTime: Date): string { return `${diff.minutes} minutes ${diff.seconds} seconds`; } -export default function Statusbar(props: Props) { + +export const Statusbar: FC = ({ + online, + lastConnectTime, + lastDisconnectTime, + viewerCount, +}) => { const [, setNow] = useState(new Date()); // Set a timer to update the status bar. @@ -33,8 +39,6 @@ export default function Statusbar(props: Props) { }; }, []); - const { online, lastConnectTime, lastDisconnectTime, viewerCount } = props; - let onlineMessage = ''; let rightSideMessage: any; if (online && lastConnectTime) { @@ -53,12 +57,13 @@ export default function Statusbar(props: Props) { } return ( -
+
{onlineMessage}
{rightSideMessage}
); -} +}; +export default Statusbar; Statusbar.defaultProps = { lastConnectTime: null, diff --git a/web/components/ui/Statusbar/index.ts b/web/components/ui/Statusbar/index.ts deleted file mode 100644 index d058c8e97..000000000 --- a/web/components/ui/Statusbar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Statusbar'; diff --git a/web/components/ui/followers/FollowerCollection/FollowerCollection.stories.tsx b/web/components/ui/followers/FollowerCollection/FollowerCollection.stories.tsx index 46a20d6fd..2c56d56e7 100644 --- a/web/components/ui/followers/FollowerCollection/FollowerCollection.stories.tsx +++ b/web/components/ui/followers/FollowerCollection/FollowerCollection.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import FollowerCollection from './FollowerCollection'; +import { FollowerCollection } from './FollowerCollection'; export default { title: 'owncast/Components/Followers/Followers collection', diff --git a/web/components/ui/followers/FollowerCollection/FollowerCollection.tsx b/web/components/ui/followers/FollowerCollection/FollowerCollection.tsx index 1348e0519..3622bf5eb 100644 --- a/web/components/ui/followers/FollowerCollection/FollowerCollection.tsx +++ b/web/components/ui/followers/FollowerCollection/FollowerCollection.tsx @@ -1,10 +1,10 @@ -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { Col, Pagination, Row } from 'antd'; import { Follower } from '../../../../interfaces/follower'; -import SingleFollower from '../SingleFollower/SingleFollower'; -import s from '../SingleFollower/SingleFollower.module.scss'; +import { SingleFollower } from '../SingleFollower/SingleFollower'; +import styles from '../SingleFollower/SingleFollower.module.scss'; -export default function FollowerCollection() { +export const FollowerCollection: FC = () => { const ENDPOINT = '/api/followers'; const ITEMS_PER_PAGE = 24; @@ -42,7 +42,7 @@ export default function FollowerCollection() { } return ( -
+
{followers.map(follower => (
@@ -62,4 +62,4 @@ export default function FollowerCollection() { /> ); -} +}; diff --git a/web/components/ui/followers/SingleFollower/SingleFollower.stories.tsx b/web/components/ui/followers/SingleFollower/SingleFollower.stories.tsx index 03cf1260b..c1dc5a681 100644 --- a/web/components/ui/followers/SingleFollower/SingleFollower.stories.tsx +++ b/web/components/ui/followers/SingleFollower/SingleFollower.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import SingleFollower from './SingleFollower'; +import { SingleFollower } from './SingleFollower'; import SingleFollowerMock from '../../../../stories/assets/mocks/single-follower.png'; export default { diff --git a/web/components/ui/followers/SingleFollower/SingleFollower.tsx b/web/components/ui/followers/SingleFollower/SingleFollower.tsx index cd487ed71..24e348d54 100644 --- a/web/components/ui/followers/SingleFollower/SingleFollower.tsx +++ b/web/components/ui/followers/SingleFollower/SingleFollower.tsx @@ -1,30 +1,26 @@ import { Avatar, Col, Row } from 'antd'; -import React from 'react'; +import React, { FC } from 'react'; import { Follower } from '../../../../interfaces/follower'; -import s from './SingleFollower.module.scss'; +import styles from './SingleFollower.module.scss'; -interface Props { +export type SingleFollowerProps = { follower: Follower; -} +}; -export default function SingleFollower(props: Props) { - const { follower } = props; - - return ( - - - Logo - - - - {follower.name} - {follower.username} - - - - - ); -} +export const SingleFollower: FC = ({ follower }) => ( + + + Logo + + + + {follower.name} + {follower.username} + + + + +); diff --git a/web/components/ui/index.tsx b/web/components/ui/index.tsx deleted file mode 100644 index 91880c9bc..000000000 --- a/web/components/ui/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export { default as Header } from './Header/index'; -export { default as Sidebar } from './Sidebar/index'; -export { default as Footer } from './Footer/index'; -export { default as Content } from './Content/index'; -export { default as ModIcon } from './ModIcon'; -export { default as ServerLogo } from './Logo'; -export { default as StatusBar } from './Statusbar'; diff --git a/web/components/video/OwncastPlayer/OwncastPlayer.stories.tsx b/web/components/video/OwncastPlayer/OwncastPlayer.stories.tsx index 7e4015879..9c2b007a3 100644 --- a/web/components/video/OwncastPlayer/OwncastPlayer.stories.tsx +++ b/web/components/video/OwncastPlayer/OwncastPlayer.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import OwncastPlayer from './OwncastPlayer'; +import { OwncastPlayer } from './OwncastPlayer'; const streams = { DemoServer: `https://watch.owncast.online/hls/stream.m3u8`, diff --git a/web/components/video/OwncastPlayer/OwncastPlayer.tsx b/web/components/video/OwncastPlayer/OwncastPlayer.tsx index d45b47dcf..c96e6ae31 100644 --- a/web/components/video/OwncastPlayer/OwncastPlayer.tsx +++ b/web/components/video/OwncastPlayer/OwncastPlayer.tsx @@ -1,9 +1,9 @@ -import React, { useEffect } from 'react'; +import React, { FC, useEffect } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { useHotkeys } from 'react-hotkeys-hook'; -import VideoJS from '../player'; +import { VideoJS } from '../VideoJS/VideoJS'; import ViewerPing from '../viewer-ping'; -import VideoPoster from '../VideoPoster/VideoPoster'; +import { VideoPoster } from '../VideoPoster/VideoPoster'; import { getLocalStorage, setLocalStorage } from '../../../utils/localStorage'; import { isVideoPlayingAtom, clockSkewAtom } from '../../stores/ClientConfigStore'; import PlaybackMetrics from '../metrics/playback'; @@ -19,10 +19,10 @@ let playbackMetrics = null; let latencyCompensator = null; let latencyCompensatorEnabled = false; -interface Props { +export type OwncastPlayerProps = { source: string; online: boolean; -} +}; async function getVideoSettings() { let qualities = []; @@ -36,9 +36,8 @@ async function getVideoSettings() { return qualities; } -export default function OwncastPlayer(props: Props) { +export const OwncastPlayer: FC = ({ source, online }) => { const playerRef = React.useRef(null); - const { source, online } = props; const [videoPlaying, setVideoPlaying] = useRecoilState(isVideoPlayingAtom); const clockSkew = useRecoilValue(clockSkewAtom); @@ -302,4 +301,5 @@ export default function OwncastPlayer(props: Props) { ); -} +}; +export default OwncastPlayer; diff --git a/web/components/video/Player.module.scss b/web/components/video/VideoJS/VideoJS.module.scss similarity index 100% rename from web/components/video/Player.module.scss rename to web/components/video/VideoJS/VideoJS.module.scss diff --git a/web/components/video/player.scss b/web/components/video/VideoJS/VideoJS.scss similarity index 97% rename from web/components/video/player.scss rename to web/components/video/VideoJS/VideoJS.scss index e1528c696..5ee84db7e 100644 --- a/web/components/video/player.scss +++ b/web/components/video/VideoJS/VideoJS.scss @@ -38,7 +38,7 @@ } .vjs-airplay .vjs-icon-placeholder::before { - content: url('./airplay.png'); + content: url('../airplay.png'); } .vjs-quality-selector .vjs-icon-placeholder { diff --git a/web/components/video/player.tsx b/web/components/video/VideoJS/VideoJS.tsx similarity index 97% rename from web/components/video/player.tsx rename to web/components/video/VideoJS/VideoJS.tsx index 4831a9766..600fcaa9f 100644 --- a/web/components/video/player.tsx +++ b/web/components/video/VideoJS/VideoJS.tsx @@ -1,21 +1,20 @@ -import React from 'react'; +import React, { FC } from 'react'; import videojs from 'video.js'; -import s from './Player.module.scss'; +import styles from './VideoJS.module.scss'; require('video.js/dist/video-js.css'); // TODO: Restore volume that was saved in local storage. // import { getLocalStorage, setLocalStorage } from '../../utils/helpers.js'; // import { PLAYER_VOLUME, URL_STREAM } from '../../utils/constants.js'; -interface Props { +export type VideoJSProps = { options: any; onReady: (player: videojs.Player, vjsInstance: videojs) => void; -} +}; -export function VideoJS(props: Props) { +export const VideoJS: FC = ({ options, onReady }) => { const videoRef = React.useRef(null); const playerRef = React.useRef(null); - const { options, onReady } = props; React.useEffect(() => { // Make sure Video.js player is only initialized once @@ -52,10 +51,13 @@ export function VideoJS(props: Props) { return (
{/* eslint-disable-next-line jsx-a11y/media-has-caption */} -
); -} +}; export default VideoJS; diff --git a/web/components/video/VideoPoster/VideoPoster.stories.tsx b/web/components/video/VideoPoster/VideoPoster.stories.tsx index dd5442ef6..090837aed 100644 --- a/web/components/video/VideoPoster/VideoPoster.stories.tsx +++ b/web/components/video/VideoPoster/VideoPoster.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import VideoPoster from './VideoPoster'; +import { VideoPoster } from './VideoPoster'; export default { title: 'owncast/Player/Video poster', diff --git a/web/components/video/VideoPoster/VideoPoster.tsx b/web/components/video/VideoPoster/VideoPoster.tsx index 376161354..1c21a3e6d 100644 --- a/web/components/video/VideoPoster/VideoPoster.tsx +++ b/web/components/video/VideoPoster/VideoPoster.tsx @@ -1,18 +1,16 @@ -import { useEffect, useState } from 'react'; -import CrossfadeImage from '../../ui/CrossfadeImage/CrossfadeImage'; -import s from './VideoPoster.module.scss'; +import { FC, useEffect, useState } from 'react'; +import { CrossfadeImage } from '../../ui/CrossfadeImage/CrossfadeImage'; +import styles from './VideoPoster.module.scss'; const REFRESH_INTERVAL = 20_000; -interface Props { +export type VideoPosterProps = { initialSrc: string; src: string; online: boolean; -} - -export default function VideoPoster(props: Props) { - const { online, initialSrc, src: base } = props; +}; +export const VideoPoster: FC = ({ online, initialSrc, src: base }) => { let timer: ReturnType; const [src, setSrc] = useState(initialSrc); const [duration, setDuration] = useState('0s'); @@ -28,7 +26,7 @@ export default function VideoPoster(props: Props) { }, []); return ( -
+
{!online && logo} {online && ( @@ -42,4 +40,4 @@ export default function VideoPoster(props: Props) { )}
); -} +}; diff --git a/web/components/video/settings-menu.ts b/web/components/video/settings-menu.ts index 43d6b3c58..e611707d2 100644 --- a/web/components/video/settings-menu.ts +++ b/web/components/video/settings-menu.ts @@ -1,9 +1,4 @@ -export default function createVideoSettingsMenuButton( - player, - videojs, - qualities, - latencyItemPressed, -): any { +export function createVideoSettingsMenuButton(player, videojs, qualities, latencyItemPressed): any { const VjsMenuItem = videojs.getComponent('MenuItem'); const MenuItem = videojs.getComponent('MenuItem'); const MenuButtonClass = videojs.getComponent('MenuButton'); @@ -111,3 +106,4 @@ export default function createVideoSettingsMenuButton( // eslint-disable-next-line consistent-return return menuButton; } +export default createVideoSettingsMenuButton; diff --git a/web/next.config.js b/web/next.config.js index 845894579..67e6b46f3 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -35,4 +35,5 @@ module.exports = withLess({ }, ]; }, + pageExtensions: ['.tsx'], }); diff --git a/web/pages/.eslintrc.js b/web/pages/.eslintrc.js new file mode 100644 index 000000000..5e639a39c --- /dev/null +++ b/web/pages/.eslintrc.js @@ -0,0 +1,11 @@ +// ESLint rules specific to writing NextJS Pages. + +module.exports = { + rules: { + // We don't care which syntax is used for NextPage definitions. + 'react/function-component-definition': 'off', + + // The default export is used by NextJS when rendering pages. + 'import/prefer-default-export': 'error', + }, +}; diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index acb660c3d..bcc77c582 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -23,17 +23,17 @@ import '../styles/chat.scss'; import '../styles/pages.scss'; import '../styles/offline-notice.scss'; -import '../components/video/player.scss'; +import '../components/video/VideoJS/VideoJS.scss'; import { AppProps } from 'next/app'; import { Router, useRouter } from 'next/router'; import { RecoilRoot } from 'recoil'; import { useEffect } from 'react'; -import AdminLayout from '../components/layouts/admin-layout'; -import SimpleLayout from '../components/layouts/SimpleLayout'; +import { AdminLayout } from '../components/layouts/AdminLayout'; +import { SimpleLayout } from '../components/layouts/SimpleLayout'; -function App({ Component, pageProps }: AppProps) { +const App = ({ Component, pageProps }: AppProps) => { useEffect(() => { if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -61,6 +61,6 @@ function App({ Component, pageProps }: AppProps) { ); -} +}; export default App; diff --git a/web/pages/admin/access-tokens.tsx b/web/pages/admin/access-tokens.tsx index 8c4d719bf..48924762b 100644 --- a/web/pages/admin/access-tokens.tsx +++ b/web/pages/admin/access-tokens.tsx @@ -62,7 +62,7 @@ interface Props { onOk: any; // todo: make better type visible: boolean; } -function NewTokenModal(props: Props) { +const NewTokenModal = (props: Props) => { const { onOk, onCancel, visible } = props; const [selectedScopes, setSelectedScopes] = useState([]); const [name, setName] = useState(''); @@ -131,9 +131,9 @@ function NewTokenModal(props: Props) {

); -} +}; -export default function AccessTokens() { +const AccessTokens = () => { const [tokens, setTokens] = useState([]); const [isTokenModalVisible, setIsTokenModalVisible] = useState(false); @@ -264,4 +264,5 @@ export default function AccessTokens() { />
); -} +}; +export default AccessTokens; diff --git a/web/pages/admin/actions.tsx b/web/pages/admin/actions.tsx index a6df1256c..59fc60c71 100644 --- a/web/pages/admin/actions.tsx +++ b/web/pages/admin/actions.tsx @@ -1,7 +1,7 @@ import { DeleteOutlined } from '@ant-design/icons'; import { Button, Checkbox, Input, Modal, Space, Table, Typography } from 'antd'; import React, { useContext, useEffect, useState } from 'react'; -import FormStatusIndicator from '../../components/config/form-status-indicator'; +import { FormStatusIndicator } from '../../components/config/FormStatusIndicator'; import { API_EXTERNAL_ACTIONS, postConfigUpdateToAPI, @@ -20,7 +20,7 @@ interface Props { visible: boolean; } -function NewActionModal(props: Props) { +const NewActionModal = (props: Props) => { const { onOk, onCancel, visible } = props; const [actionUrl, setActionUrl] = useState(''); @@ -131,9 +131,9 @@ function NewActionModal(props: Props) { ); -} +}; -export default function Actions() { +const Actions = () => { const serverStatusData = useContext(ServerStatusContext); const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { externalActions } = serverConfig; @@ -310,4 +310,5 @@ export default function Actions() { /> ); -} +}; +export default Actions; diff --git a/web/pages/admin/chat/messages.tsx b/web/pages/admin/chat/messages.tsx index 84dd24f31..99c861315 100644 --- a/web/pages/admin/chat/messages.tsx +++ b/web/pages/admin/chat/messages.tsx @@ -13,8 +13,8 @@ import { UPDATE_CHAT_MESSGAE_VIZ, } from '../../../utils/apis'; import { isEmptyObject } from '../../../utils/format'; -import MessageVisiblityToggle from '../../../components/message-visiblity-toggle'; -import UserPopover from '../../../components/user-popover'; +import { MessageVisiblityToggle } from '../../../components/MessageVisiblityToggle'; +import { UserPopover } from '../../../components/UserPopover'; const { Title } = Typography; diff --git a/web/pages/admin/chat/users.tsx b/web/pages/admin/chat/users.tsx index 4178e7538..a7bc3acae 100644 --- a/web/pages/admin/chat/users.tsx +++ b/web/pages/admin/chat/users.tsx @@ -8,9 +8,9 @@ import { MODERATORS, BANNED_IPS, } from '../../../utils/apis'; -import UserTable from '../../../components/user-table'; -import ClientTable from '../../../components/client-table'; -import BannedIPsTable from '../../../components/banned-ips-table'; +import { UserTable } from '../../../components/UserTable'; +import { ClientTable } from '../../../components/ClientTable'; +import { BannedIPsTable } from '../../../components/BannedIPsTable'; const { TabPane } = Tabs; diff --git a/web/pages/admin/config-chat.tsx b/web/pages/admin/config-chat.tsx index e57762a5b..7ac37f863 100644 --- a/web/pages/admin/config-chat.tsx +++ b/web/pages/admin/config-chat.tsx @@ -1,9 +1,9 @@ import { Typography } from 'antd'; import React, { useContext, useEffect, useState } from 'react'; -import { TEXTFIELD_TYPE_TEXTAREA } from '../../components/config/form-textfield'; -import TextFieldWithSubmit from '../../components/config/form-textfield-with-submit'; -import ToggleSwitch from '../../components/config/form-toggleswitch'; -import EditValueArray from '../../components/config/edit-string-array'; +import { TEXTFIELD_TYPE_TEXTAREA } from '../../components/config/TextField'; +import { TextFieldWithSubmit } from '../../components/config/TextFieldWithSubmit'; +import { ToggleSwitch } from '../../components/config/ToggleSwitch'; +import { EditValueArray } from '../../components/config/EditValueArray'; import { createInputStatus, StatusState, diff --git a/web/pages/admin/config-federation.tsx b/web/pages/admin/config-federation.tsx index a82aed00e..f2ca41c3f 100644 --- a/web/pages/admin/config-federation.tsx +++ b/web/pages/admin/config-federation.tsx @@ -6,10 +6,10 @@ import { TEXTFIELD_TYPE_TEXT, TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL, -} from '../../components/config/form-textfield'; -import TextFieldWithSubmit from '../../components/config/form-textfield-with-submit'; -import ToggleSwitch from '../../components/config/form-toggleswitch'; -import EditValueArray from '../../components/config/edit-string-array'; +} from '../../components/config/TextField'; +import { TextFieldWithSubmit } from '../../components/config/TextFieldWithSubmit'; +import { ToggleSwitch } from '../../components/config/ToggleSwitch'; +import { EditValueArray } from '../../components/config/EditValueArray'; import { UpdateArgs } from '../../types/config-section'; import { FIELD_PROPS_ENABLE_FEDERATION, @@ -27,72 +27,70 @@ import { import { ServerStatusContext } from '../../utils/server-status-context'; import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses'; -function FederationInfoModal({ cancelPressed, okPressed }) { - return ( - - - - - } - > - How do Owncast's social features work? - - Owncast's social features are accomplished by having your server join The{' '} - - Fediverse - - , a decentralized, open, collection of independent servers, like yours. - - Please{' '} +const FederationInfoModal = ({ cancelPressed, okPressed }) => ( + + + + + } + > + How do Owncast's social features work? + + Owncast's social features are accomplished by having your server join The{' '} + + Fediverse + + , a decentralized, open, collection of independent servers, like yours. + + Please{' '} + + read more + {' '} + about these features, the details behind them, and how they work. + + What do you need to know? +
    +
  • + These features are brand new. Given the variability of interfacing with the rest of the + world, bugs are possible. Please report anything that you think isn't working quite right. +
  • +
  • You must always host your Owncast server with SSL using a https url.
  • +
  • + You should not change your server name URL or social username once people begin following + you, as your server will be seen as a completely different user on the Fediverse, and the + old user will disappear. +
  • +
  • + Turning on Private mode will allow you to manually approve each follower and limit + the visibility of your posts to followers only. +
  • +
+ Learn more about The Fediverse + + If these concepts are new you should discover more about what this functionality has to offer. + Visit{' '} - read more + our documentation {' '} - about these features, the details behind them, and how they work. - - What do you need to know? -
    -
  • - These features are brand new. Given the variability of interfacing with the rest of the - world, bugs are possible. Please report anything that you think isn't working quite right. -
  • -
  • You must always host your Owncast server with SSL using a https url.
  • -
  • - You should not change your server name URL or social username once people begin following - you, as your server will be seen as a completely different user on the Fediverse, and the - old user will disappear. -
  • -
  • - Turning on Private mode will allow you to manually approve each follower and limit - the visibility of your posts to followers only. -
  • -
- Learn more about The Fediverse - - If these concepts are new you should discover more about what this functionality has to - offer. Visit{' '} - - our documentation - {' '} - to be pointed at some resources that will help get you started on The Fediverse. - -
- ); -} + to be pointed at some resources that will help get you started on The Fediverse. + +
+); FederationInfoModal.propTypes = { cancelPressed: PropTypes.func.isRequired, okPressed: PropTypes.func.isRequired, }; -export default function ConfigFederation() { +const ConfigFederation = () => { const { Title } = Typography; const [formDataValues, setFormDataValues] = useState(null); const [isInfoModalOpen, setIsInfoModalOpen] = useState(false); @@ -331,4 +329,5 @@ export default function ConfigFederation() { )} ); -} +}; +export default ConfigFederation; diff --git a/web/pages/admin/config-notify.tsx b/web/pages/admin/config-notify.tsx index 6247d7227..ddbec4ae0 100644 --- a/web/pages/admin/config-notify.tsx +++ b/web/pages/admin/config-notify.tsx @@ -6,9 +6,10 @@ import Discord from '../../components/config/notification/discord'; import Browser from '../../components/config/notification/browser'; import Twitter from '../../components/config/notification/twitter'; import Federation from '../../components/config/notification/federation'; -import TextFieldWithSubmit, { +import { + TextFieldWithSubmit, TEXTFIELD_TYPE_URL, -} from '../../components/config/form-textfield-with-submit'; +} from '../../components/config/TextFieldWithSubmit'; import { TEXTFIELD_PROPS_FEDERATION_INSTANCE_URL } from '../../utils/config-constants'; import { ServerStatusContext } from '../../utils/server-status-context'; import { UpdateArgs } from '../../types/config-section'; diff --git a/web/pages/admin/config-public-details.tsx b/web/pages/admin/config-public-details.tsx index 374650821..492b58aba 100644 --- a/web/pages/admin/config-public-details.tsx +++ b/web/pages/admin/config-public-details.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Typography } from 'antd'; -import EditInstanceDetails from '../../components/config/edit-instance-details'; -import EditInstanceTags from '../../components/config/edit-tags'; -import EditSocialLinks from '../../components/config/edit-social-links'; -import EditPageContent from '../../components/config/edit-page-content'; -import EditCustomStyles from '../../components/config/edit-custom-css'; +import { EditInstanceDetails } from '../../components/config/EditInstanceDetails'; +import { EditInstanceTags } from '../../components/config/EditInstanceTags'; +import { EditSocialLinks } from '../../components/config/EditSocialLinks'; +import { EditPageContent } from '../../components/config/EditPageContent'; +import { EditCustomStyles } from '../../components/config/EditCustomStyles'; const { Title } = Typography; diff --git a/web/pages/admin/config-server-details.tsx b/web/pages/admin/config-server-details.tsx index 0d1f9df5b..c835ad433 100644 --- a/web/pages/admin/config-server-details.tsx +++ b/web/pages/admin/config-server-details.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Typography } from 'antd'; -import EditServerDetails from '../../components/config/edit-server-details'; +import { EditInstanceDetails } from '../../components/config/EditInstanceDetails2'; const { Title } = Typography; @@ -13,7 +13,7 @@ export default function ConfigServerDetails() { it's likely the other settings will not need to be changed.

- +
); diff --git a/web/pages/admin/config-social-items.tsx b/web/pages/admin/config-social-items.tsx index 94d2614c2..8f8248ebb 100644 --- a/web/pages/admin/config-social-items.tsx +++ b/web/pages/admin/config-social-items.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Typography } from 'antd'; -import EditSocialLinks from '../../components/config/edit-social-links'; +import { EditSocialLinks } from '../../components/config/EditSocialLinks'; const { Title } = Typography; diff --git a/web/pages/admin/config-storage.tsx b/web/pages/admin/config-storage.tsx index 432a2a272..02ec3ac61 100644 --- a/web/pages/admin/config-storage.tsx +++ b/web/pages/admin/config-storage.tsx @@ -1,6 +1,6 @@ import { Typography } from 'antd'; import React from 'react'; -import EditStorage from '../../components/config/edit-storage'; +import { EditStorage } from '../../components/config/EditStorage'; const { Title } = Typography; diff --git a/web/pages/admin/config-video.tsx b/web/pages/admin/config-video.tsx index b22fbd4cb..87e457cc8 100644 --- a/web/pages/admin/config-video.tsx +++ b/web/pages/admin/config-video.tsx @@ -1,8 +1,8 @@ import { Col, Collapse, Row, Typography } from 'antd'; import React from 'react'; -import VideoCodecSelector from '../../components/config/video-codec-selector'; -import VideoLatency from '../../components/config/video-latency'; -import VideoVariantsTable from '../../components/config/video-variants-table'; +import { CodecSelector as VideoCodecSelector } from '../../components/config/CodecSelector'; +import { VideoLatency } from '../../components/config/VideoLatency'; +import { CurrentVariantsTable } from '../../components/config/CurrentVariantsTable'; const { Panel } = Collapse; const { Title } = Typography; @@ -28,7 +28,7 @@ export default function ConfigVideoSettings() {
- +
diff --git a/web/pages/admin/hardware-info.tsx b/web/pages/admin/hardware-info.tsx index c048ce39e..7f2e86519 100644 --- a/web/pages/admin/hardware-info.tsx +++ b/web/pages/admin/hardware-info.tsx @@ -2,8 +2,8 @@ import { BulbOutlined, LaptopOutlined, SaveOutlined } from '@ant-design/icons'; import { Row, Col, Typography } from 'antd'; import React, { useEffect, useState } from 'react'; import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../../utils/apis'; -import Chart from '../../components/chart'; -import StatisticItem from '../../components/statistic'; +import { Chart } from '../../components/Chart'; +import { StatisticItem } from '../../components/StatisticItem'; // TODO: FIX TS WARNING FROM THIS. // interface TimedValue { diff --git a/web/pages/admin/index.tsx b/web/pages/admin/index.tsx index d9dcc5b69..759af68f1 100644 --- a/web/pages/admin/index.tsx +++ b/web/pages/admin/index.tsx @@ -3,13 +3,13 @@ import { Skeleton, Card, Statistic, Row, Col } from 'antd'; import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons'; import { formatDistanceToNow, formatRelative } from 'date-fns'; import { ServerStatusContext } from '../../utils/server-status-context'; -import LogTable from '../../components/log-table'; -import Offline from '../../components/offline-notice'; -import StreamHealthOverview from '../../components/stream-health-overview'; +import { LogTable } from '../../components/LogTable'; +import { Offline } from '../../components/Offline'; +import { StreamHealthOverview } from '../../components/StreamHealthOverview'; import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis'; import { formatIPAddress, isEmptyObject } from '../../utils/format'; -import NewsFeed from '../../components/news-feed'; +import { NewsFeed } from '../../components/NewsFeed'; function streamDetailsFormatter(streamDetails) { return ( diff --git a/web/pages/admin/logs.tsx b/web/pages/admin/logs.tsx index 084a33137..f9d774752 100644 --- a/web/pages/admin/logs.tsx +++ b/web/pages/admin/logs.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import LogTable from '../../components/log-table'; +import { LogTable } from '../../components/LogTable'; import { LOGS_ALL, fetchData } from '../../utils/apis'; diff --git a/web/pages/admin/stream-health.tsx b/web/pages/admin/stream-health.tsx index 437fae0b0..1d1dde438 100644 --- a/web/pages/admin/stream-health.tsx +++ b/web/pages/admin/stream-health.tsx @@ -4,8 +4,8 @@ import { Row, Col, Typography, Space, Statistic, Card, Alert, Spin } from 'antd' import React, { ReactNode, useEffect, useState } from 'react'; import { ClockCircleOutlined, WarningOutlined, WifiOutlined } from '@ant-design/icons'; import { fetchData, FETCH_INTERVAL, API_STREAM_HEALTH_METRICS } from '../../utils/apis'; -import Chart from '../../components/chart'; -import StreamHealthOverview from '../../components/stream-health-overview'; +import { Chart } from '../../components/Chart'; +import { StreamHealthOverview } from '../../components/StreamHealthOverview'; interface TimedValue { time: Date; @@ -17,16 +17,14 @@ interface DescriptionBoxProps { description: ReactNode; } -function DescriptionBox({ title, description }: DescriptionBoxProps) { - return ( -
- {title} - {description} -
- ); -} +const DescriptionBox = ({ title, description }: DescriptionBoxProps) => ( +
+ {title} + {description} +
+); -export default function StreamHealth() { +const StreamHealth = () => { const [errors, setErrors] = useState([]); const [qualityVariantChanges, setQualityVariantChanges] = useState([]); @@ -409,4 +407,5 @@ export default function StreamHealth() { ); -} +}; +export default StreamHealth; diff --git a/web/pages/admin/upgrade.tsx b/web/pages/admin/upgrade.tsx index fa38c218c..134531e67 100644 --- a/web/pages/admin/upgrade.tsx +++ b/web/pages/admin/upgrade.tsx @@ -5,7 +5,7 @@ import { getGithubRelease } from '../../utils/apis'; const { Title } = Typography; -function AssetTable(assets) { +const AssetTable = assets => { const data = Object.values(assets) as object[]; const columns = [ @@ -32,9 +32,9 @@ function AssetTable(assets) { pagination={false} /> ); -} +}; -export default function Logs() { +const Logs = () => { const [release, setRelease] = useState({ html_url: '', name: '', @@ -71,4 +71,5 @@ export default function Logs() { ); -} +}; +export default Logs; diff --git a/web/pages/admin/viewer-info.tsx b/web/pages/admin/viewer-info.tsx index 24a905d8b..58cbbe757 100644 --- a/web/pages/admin/viewer-info.tsx +++ b/web/pages/admin/viewer-info.tsx @@ -2,9 +2,9 @@ import React, { useState, useEffect, useContext } from 'react'; import { Row, Col, Typography, Menu, Dropdown, Spin, Alert } from 'antd'; import { DownOutlined, UserOutlined } from '@ant-design/icons'; import { getUnixTime, sub } from 'date-fns'; -import Chart from '../../components/chart'; -import StatisticItem from '../../components/statistic'; -import ViewerTable from '../../components/viewer-table'; +import { Chart } from '../../components/Chart'; +import { StatisticItem } from '../../components/StatisticItem'; +import { ViewerTable } from '../../components/ViewerTable'; import { ServerStatusContext } from '../../utils/server-status-context'; diff --git a/web/pages/admin/webhooks.tsx b/web/pages/admin/webhooks.tsx index 3db7ef67d..78967f43c 100644 --- a/web/pages/admin/webhooks.tsx +++ b/web/pages/admin/webhooks.tsx @@ -55,7 +55,7 @@ interface Props { visible: boolean; } -function NewWebhookModal(props: Props) { +const NewWebhookModal = (props: Props) => { const { onOk, onCancel, visible } = props; const [selectedEvents, setSelectedEvents] = useState([]); @@ -121,9 +121,9 @@ function NewWebhookModal(props: Props) {

); -} +}; -export default function Webhooks() { +const Webhooks = () => { const [webhooks, setWebhooks] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); @@ -247,4 +247,5 @@ export default function Webhooks() { /> ); -} +}; +export default Webhooks; diff --git a/web/pages/embed/chat/readonly/index.tsx b/web/pages/embed/chat/readonly/index.tsx index b0ca88b32..af7d669f1 100644 --- a/web/pages/embed/chat/readonly/index.tsx +++ b/web/pages/embed/chat/readonly/index.tsx @@ -1,6 +1,6 @@ import { useRecoilValue } from 'recoil'; import { ChatMessage } from '../../../../interfaces/chat-message.model'; -import ChatContainer from '../../../../components/chat/ChatContainer/ChatContainer'; +import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer'; import { ClientConfigStore, chatDisplayNameAtom, diff --git a/web/pages/embed/video/index.tsx b/web/pages/embed/video/index.tsx index 1adf6cce8..03638ba32 100644 --- a/web/pages/embed/video/index.tsx +++ b/web/pages/embed/video/index.tsx @@ -6,9 +6,9 @@ import { isOnlineSelector, serverStatusState, } from '../../../components/stores/ClientConfigStore'; -import OfflineBanner from '../../../components/ui/OfflineBanner/OfflineBanner'; -import Statusbar from '../../../components/ui/Statusbar/Statusbar'; -import OwncastPlayer from '../../../components/video/OwncastPlayer/OwncastPlayer'; +import { OfflineBanner } from '../../../components/ui/OfflineBanner/OfflineBanner'; +import { Statusbar } from '../../../components/ui/Statusbar/Statusbar'; +import { OwncastPlayer } from '../../../components/video/OwncastPlayer/OwncastPlayer'; import { ClientConfig } from '../../../interfaces/client-config.model'; import { ServerStatus } from '../../../interfaces/server-status.model'; diff --git a/web/pages/index.tsx b/web/pages/index.tsx index f0367c0ca..33a1e74c5 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -1,4 +1,4 @@ -import Main from '../components/layouts/Main'; +import { Main } from '../components/layouts/Main'; export default function Home() { return
; diff --git a/web/stories/PageLogo.stories.tsx b/web/stories/PageLogo.stories.tsx index acb743f32..fc4ea276b 100644 --- a/web/stories/PageLogo.stories.tsx +++ b/web/stories/PageLogo.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import Logo from '../components/ui/Logo/Logo'; +import { Logo } from '../components/ui/Logo/Logo'; export default { title: 'owncast/Components/Page Logo', diff --git a/web/utils/config-constants.tsx b/web/utils/config-constants.tsx index c75c512ed..d2acb60d4 100644 --- a/web/utils/config-constants.tsx +++ b/web/utils/config-constants.tsx @@ -1,7 +1,7 @@ // DEFAULT VALUES import { fetchData, SERVER_CONFIG_UPDATE_URL } from './apis'; import { ApiPostArgs, VideoVariant, SocialHandle } from '../types/config-section'; -import { TEXTFIELD_TYPE_URL } from '../components/config/form-textfield'; +import { TEXTFIELD_TYPE_URL } from '../components/config/TextField'; import { DEFAULT_TEXTFIELD_URL_PATTERN } from './urls'; export const TEXT_MAXLENGTH = 255;