123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- import React, { useState, useContext, useEffect } from 'react';
- import { Typography, Table, Button, Modal, Input } from 'antd';
- import { ColumnsType } from 'antd/lib/table';
- import dynamic from 'next/dynamic';
- import { SocialDropdown } from '../../SocialDropdown';
- import { fetchData, SOCIAL_PLATFORMS_LIST } from '../../../../utils/apis';
- import { ServerStatusContext } from '../../../../utils/server-status-context';
- 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 {
- isValidMatrixAccount,
- isValidAccount,
- isValidUrl,
- DEFAULT_TEXTFIELD_URL_PATTERN,
- } from '../../../../utils/validators';
- import { TextField } from '../../TextField';
- import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../../../utils/input-statuses';
- import { FormStatusIndicator } from '../../FormStatusIndicator';
- const { Title } = Typography;
- // Lazy loaded components
- const CaretDownOutlined = dynamic(() => import('@ant-design/icons/CaretDownOutlined'), {
- ssr: false,
- });
- const CaretUpOutlined = dynamic(() => import('@ant-design/icons/CaretUpOutlined'), {
- ssr: false,
- });
- const DeleteOutlined = dynamic(() => import('@ant-design/icons/DeleteOutlined'), {
- ssr: false,
- });
- // eslint-disable-next-line react/function-component-definition
- export default function EditSocialLinks() {
- const [availableIconsList, setAvailableIconsList] = useState([]);
- const [currentSocialHandles, setCurrentSocialHandles] = useState([]);
- const [displayModal, setDisplayModal] = useState(false);
- const [displayOther, setDisplayOther] = useState(false);
- const [modalProcessing, setModalProcessing] = useState(false);
- const [editId, setEditId] = useState(-1);
- // current data inside modal
- const [modalDataState, setModalDataState] = useState(DEFAULT_SOCIAL_HANDLE);
- const [submitStatus, setSubmitStatus] = useState(null);
- const serverStatusData = useContext(ServerStatusContext);
- const { serverConfig, setFieldInConfigState } = serverStatusData || {};
- const { instanceDetails } = serverConfig;
- const { socialHandles: initialSocialHandles } = instanceDetails;
- let resetTimer = null;
- const PLACEHOLDERS = {
- mastodon: 'https://mastodon.social/@username',
- twitter: 'https://twitter.com/username',
- };
- 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) {
- console.log(error);
- // do nothing
- }
- };
- const isPredefinedSocial = (platform: string) =>
- availableIconsList.find(item => item.key === platform) || false;
- const selectedOther =
- modalDataState.platform !== '' &&
- !availableIconsList.find(item => item.key === modalDataState.platform);
- useEffect(() => {
- getAvailableIcons();
- }, []);
- useEffect(() => {
- if (instanceDetails.socialHandles) {
- setCurrentSocialHandles(initialSocialHandles);
- }
- }, [instanceDetails]);
- const resetStates = () => {
- setSubmitStatus(null);
- resetTimer = null;
- clearTimeout(resetTimer);
- };
- const resetModal = () => {
- setDisplayModal(false);
- setEditId(-1);
- setDisplayOther(false);
- setModalProcessing(false);
- setModalDataState({ ...DEFAULT_SOCIAL_HANDLE });
- };
- 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);
- };
- const handleUrlChange = ({ value }: UpdateArgs) => {
- updateModalState('url', value);
- };
- // 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: () => {
- setFieldInConfigState({
- fieldName: 'socialHandles',
- value: postValue,
- path: 'instanceDetails',
- });
- // close modal
- setModalProcessing(false);
- handleModalCancel();
- setSubmitStatus(createInputStatus(STATUS_SUCCESS));
- resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
- },
- onError: (message: string) => {
- setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
- setModalProcessing(false);
- resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
- },
- });
- };
- // on Ok, send all of dataState to api
- // show loading
- // close modal when api is done
- const handleModalOk = () => {
- setModalProcessing(true);
- const postData = currentSocialHandles.length ? [...currentSocialHandles] : [];
- if (editId === -1) {
- postData.push(modalDataState);
- } else {
- postData.splice(editId, 1, modalDataState);
- }
- postUpdateToAPI(postData);
- };
- const handleDeleteItem = (index: number) => {
- const postData = [...currentSocialHandles];
- postData.splice(index, 1);
- postUpdateToAPI(postData);
- };
- 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);
- };
- const socialHandlesColumns: ColumnsType<SocialHandle> = [
- {
- title: 'Social Link',
- dataIndex: '',
- key: 'combo',
- render: (_, record) => {
- const { platform, url } = record;
- const platformInfo = isPredefinedSocial(platform);
- // custom platform case
- if (!platformInfo) {
- return (
- <div className="social-handle-cell">
- <p className="option-label">
- <strong>{platform}</strong>
- <span className="handle-url" title={url}>
- {url}
- </span>
- </p>
- </div>
- );
- }
- const { icon, platform: platformName } = platformInfo;
- return (
- <div className="social-handle-cell">
- <span className="option-icon">
- <img src={icon} alt="" className="option-icon" />
- </span>
- <p className="option-label">
- <strong>{platformName}</strong>
- <span className="handle-url" title={url}>
- {url}
- </span>
- </p>
- </div>
- );
- },
- },
- {
- title: '',
- dataIndex: '',
- key: 'edit',
- render: (_, _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>
- ),
- },
- ];
- const isValid = (url: string, platform: string) => {
- if (platform === 'xmpp') {
- return isValidAccount(url, 'xmpp');
- }
- if (platform === 'matrix') {
- return isValidMatrixAccount(url);
- }
- return isValidUrl(url);
- };
- const okButtonProps = {
- disabled: !isValid(modalDataState.url, modalDataState.platform),
- };
- 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>
- );
- return (
- <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>
- <FormStatusIndicator status={submitStatus} />
- <Table
- className="social-handles-table"
- pagination={false}
- size="small"
- rowKey={record => `${record.platform}-${record.url}`}
- columns={socialHandlesColumns}
- dataSource={currentSocialHandles}
- />
- <Modal
- title="Edit Social Handle"
- open={displayModal}
- onOk={handleModalOk}
- onCancel={handleModalCancel}
- confirmLoading={modalProcessing}
- okButtonProps={okButtonProps}
- >
- <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>
- </Modal>
- <br />
- <Button
- type="primary"
- onClick={() => {
- resetModal();
- setDisplayModal(true);
- }}
- >
- Add a new social link
- </Button>
- </div>
- );
- }
|