owncast/web/components/config/EditSocialLinks.tsx

354 lines
10 KiB
TypeScript
Raw Normal View History

reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
import React, { useState, useContext, useEffect, FC } from 'react';
2021-01-24 12:22:28 +03:00
import { Typography, Table, Button, Modal, Input } from 'antd';
2021-01-24 07:16:01 +03:00
import { ColumnsType } from 'antd/lib/table';
import { CaretDownOutlined, CaretUpOutlined, DeleteOutlined } from '@ant-design/icons';
reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
import { SocialDropdown } from './SocialDropdown';
import { fetchData, SOCIAL_PLATFORMS_LIST, NEXT_PUBLIC_API_HOST } from '../../utils/apis';
import { ServerStatusContext } from '../../utils/server-status-context';
2021-01-31 12:38:20 +03:00
import {
API_SOCIAL_HANDLES,
postConfigUpdateToAPI,
RESET_TIMEOUT,
DEFAULT_SOCIAL_HANDLE,
OTHER_SOCIAL_HANDLE_OPTION,
} from '../../utils/config-constants';
import { SocialHandle, UpdateArgs } from '../../types/config-section';
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls';
reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
import { TextField } from './TextField';
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
import { FormStatusIndicator } from './FormStatusIndicator';
2021-01-19 21:34:06 +03:00
const { Title } = Typography;
reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
export const EditSocialLinks: FC = () => {
2021-01-19 21:34:06 +03:00
const [availableIconsList, setAvailableIconsList] = useState([]);
const [currentSocialHandles, setCurrentSocialHandles] = useState([]);
2021-01-24 07:16:01 +03:00
const [displayModal, setDisplayModal] = useState(false);
2021-01-24 12:22:28 +03:00
const [displayOther, setDisplayOther] = useState(false);
2021-01-24 07:16:01 +03:00
const [modalProcessing, setModalProcessing] = useState(false);
2021-01-24 12:22:28 +03:00
const [editId, setEditId] = useState(-1);
2021-01-31 12:38:20 +03:00
2021-01-24 07:16:01 +03:00
// current data inside modal
const [modalDataState, setModalDataState] = useState(DEFAULT_SOCIAL_HANDLE);
const [submitStatus, setSubmitStatus] = useState(null);
2021-01-19 21:34:06 +03:00
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
const { instanceDetails } = serverConfig;
const { socialHandles: initialSocialHandles } = instanceDetails;
2021-01-24 07:16:01 +03:00
let resetTimer = null;
const PLACEHOLDERS = {
mastodon: 'https://mastodon.social/@username',
twitter: 'https://twitter.com/username',
};
2021-01-19 21:34:06 +03:00
const getAvailableIcons = async () => {
try {
const result = await fetchData(SOCIAL_PLATFORMS_LIST, { auth: false });
const list = Object.keys(result).map(item => ({
key: item,
...result[item],
}));
setAvailableIconsList(list);
} catch (error) {
2021-01-31 12:38:20 +03:00
console.log(error);
2021-01-19 21:34:06 +03:00
// do nothing
}
};
const isPredefinedSocial = (platform: string) =>
availableIconsList.find(item => item.key === platform) || false;
2021-01-31 12:38:20 +03:00
const selectedOther =
modalDataState.platform !== '' &&
!availableIconsList.find(item => item.key === modalDataState.platform);
2021-01-24 12:22:28 +03:00
2021-01-19 21:34:06 +03:00
useEffect(() => {
getAvailableIcons();
}, []);
useEffect(() => {
2021-01-24 12:22:28 +03:00
if (instanceDetails.socialHandles) {
setCurrentSocialHandles(initialSocialHandles);
}
2021-01-19 21:34:06 +03:00
}, [instanceDetails]);
2021-01-24 07:16:01 +03:00
const resetStates = () => {
setSubmitStatus(null);
resetTimer = null;
clearTimeout(resetTimer);
2021-01-24 12:22:28 +03:00
};
const resetModal = () => {
2021-01-24 07:16:01 +03:00
setDisplayModal(false);
setEditId(-1);
2021-01-24 12:22:28 +03:00
setDisplayOther(false);
setModalProcessing(false);
2021-01-31 12:38:20 +03:00
setModalDataState({ ...DEFAULT_SOCIAL_HANDLE });
2021-01-24 12:22:28 +03:00
};
const handleModalCancel = () => {
resetModal();
};
const updateModalState = (fieldName: string, value: string) => {
setModalDataState({
...modalDataState,
[fieldName]: value,
});
};
const handleDropdownSelect = (value: string) => {
if (value === OTHER_SOCIAL_HANDLE_OPTION) {
setDisplayOther(true);
updateModalState('platform', '');
} else {
setDisplayOther(false);
updateModalState('platform', value);
}
};
const handleOtherNameChange = event => {
const { value } = event.target;
updateModalState('platform', value);
};
2021-02-01 09:07:00 +03:00
const handleUrlChange = ({ value }: UpdateArgs) => {
2021-01-24 12:22:28 +03:00
updateModalState('url', value);
};
2021-01-24 07:16:01 +03:00
// posts all the variants at once as an array obj
const postUpdateToAPI = async (postValue: any) => {
await postConfigUpdateToAPI({
apiPath: API_SOCIAL_HANDLES,
data: { value: postValue },
onSuccess: () => {
2021-01-31 12:38:20 +03:00
setFieldInConfigState({
fieldName: 'socialHandles',
value: postValue,
path: 'instanceDetails',
});
2021-01-24 07:16:01 +03:00
// close modal
setModalProcessing(false);
handleModalCancel();
2021-02-01 09:07:00 +03:00
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
2021-01-24 07:16:01 +03:00
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
onError: (message: string) => {
2021-02-01 09:07:00 +03:00
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
setModalProcessing(false);
2021-01-24 07:16:01 +03:00
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
});
};
// on Ok, send all of dataState to api
// show loading
// close modal when api is done
const handleModalOk = () => {
2021-01-31 12:38:20 +03:00
setModalProcessing(true);
const postData = currentSocialHandles.length ? [...currentSocialHandles] : [];
2021-01-24 07:16:01 +03:00
if (editId === -1) {
postData.push(modalDataState);
} else {
postData.splice(editId, 1, modalDataState);
}
postUpdateToAPI(postData);
};
const handleDeleteItem = (index: number) => {
2021-01-31 12:38:20 +03:00
const postData = [...currentSocialHandles];
2021-01-24 07:16:01 +03:00
postData.splice(index, 1);
2021-01-24 12:22:28 +03:00
postUpdateToAPI(postData);
2021-01-24 07:16:01 +03:00
};
const handleMoveItemUp = (index: number) => {
if (index <= 0 || index >= currentSocialHandles.length) {
return;
}
const postData = [...currentSocialHandles];
const tmp = postData[index - 1];
postData[index - 1] = postData[index];
postData[index] = tmp;
postUpdateToAPI(postData);
};
const handleMoveItemDown = (index: number) => {
if (index < 0 || index >= currentSocialHandles.length - 1) {
return;
}
const postData = [...currentSocialHandles];
const tmp = postData[index + 1];
postData[index + 1] = postData[index];
postData[index] = tmp;
postUpdateToAPI(postData);
};
2021-01-31 12:38:20 +03:00
const socialHandlesColumns: ColumnsType<SocialHandle> = [
2021-01-24 07:16:01 +03:00
{
title: 'Social Link',
dataIndex: '',
key: 'combo',
render: (data, record) => {
const { platform, url } = record;
const platformInfo = isPredefinedSocial(platform);
// custom platform case
2021-01-24 07:16:01 +03:00
if (!platformInfo) {
return (
<div className="social-handle-cell">
<p className="option-label">
<strong>{platform}</strong>
2021-05-23 09:28:21 +03:00
<span className="handle-url" title={url}>
{url}
</span>
</p>
</div>
);
2021-01-24 07:16:01 +03:00
}
const { icon, platform: platformName } = platformInfo;
const iconUrl = `${NEXT_PUBLIC_API_HOST}${icon.slice(1)}`;
2021-01-24 07:16:01 +03:00
return (
<div className="social-handle-cell">
2021-01-24 07:16:01 +03:00
<span className="option-icon">
2021-05-23 09:28:21 +03:00
<img src={iconUrl} alt="" className="option-icon" />
2021-01-24 07:16:01 +03:00
</span>
<p className="option-label">
<strong>{platformName}</strong>
2021-05-23 09:28:21 +03:00
<span className="handle-url" title={url}>
{url}
</span>
</p>
</div>
2021-01-24 07:16:01 +03:00
);
},
},
{
title: '',
dataIndex: '',
key: 'edit',
render: (data, record, index) => (
<div className="actions">
<Button
size="small"
onClick={() => {
const platformInfo = currentSocialHandles[index];
setEditId(index);
setModalDataState({ ...platformInfo });
setDisplayModal(true);
if (!isPredefinedSocial(platformInfo.platform)) {
setDisplayOther(true);
}
}}
>
Edit
</Button>
<Button
icon={<CaretUpOutlined />}
size="small"
hidden={index === 0}
onClick={() => handleMoveItemUp(index)}
/>
<Button
icon={<CaretDownOutlined />}
size="small"
hidden={index === currentSocialHandles.length - 1}
onClick={() => handleMoveItemDown(index)}
/>
<Button
className="delete-button"
icon={<DeleteOutlined />}
size="small"
onClick={() => handleDeleteItem(index)}
/>
</div>
),
2021-01-31 12:38:20 +03:00
},
];
const okButtonProps = {
2021-01-31 12:38:20 +03:00
disabled: !isValidUrl(modalDataState.url),
};
const otherField = (
<div className="other-field-container formfield-container">
<div className="label-side" />
<div className="input-side">
<Input
placeholder="Other platform name"
defaultValue={modalDataState.platform}
onChange={handleOtherNameChange}
/>
</div>
</div>
);
2021-01-19 21:34:06 +03:00
return (
2021-02-01 09:07:00 +03:00
<div className="social-links-edit-container">
<Title level={3} className="section-title">
Your Social Handles
</Title>
<p className="description">
Add all your social media handles and links to your other profiles here.
</p>
2021-01-24 07:16:01 +03:00
<FormStatusIndicator status={submitStatus} />
2021-01-24 07:16:01 +03:00
<Table
className="social-handles-table"
2021-01-24 07:16:01 +03:00
pagination={false}
size="small"
rowKey={record => `${record.platform}-${record.url}`}
2021-01-24 07:16:01 +03:00
columns={socialHandlesColumns}
dataSource={currentSocialHandles}
/>
<Modal
title="Edit Social Handle"
visible={displayModal}
onOk={handleModalOk}
onCancel={handleModalCancel}
confirmLoading={modalProcessing}
okButtonProps={okButtonProps}
2021-01-24 07:16:01 +03:00
>
<div className="social-handle-modal-content">
<SocialDropdown
iconList={availableIconsList}
selectedOption={selectedOther ? OTHER_SOCIAL_HANDLE_OPTION : modalDataState.platform}
onSelected={handleDropdownSelect}
/>
{displayOther && otherField}
<br />
<TextField
fieldName="social-url"
label="URL"
placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'}
value={modalDataState.url}
onChange={handleUrlChange}
useTrim
type="url"
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
/>
<FormStatusIndicator status={submitStatus} />
</div>
2021-01-24 07:16:01 +03:00
</Modal>
<br />
2021-01-31 12:38:20 +03:00
<Button
type="primary"
onClick={() => {
2021-01-24 12:22:28 +03:00
resetModal();
2021-01-24 07:16:01 +03:00
setDisplayModal(true);
2021-01-31 12:38:20 +03:00
}}
>
2021-01-24 12:22:28 +03:00
Add a new social link
2021-01-24 07:16:01 +03:00
</Button>
2021-01-19 21:34:06 +03:00
</div>
2021-01-31 12:38:20 +03:00
);
reafctor: normalize component formatting (#2082) * refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
2022-09-07 10:00:28 +03:00
};