11 changed files with 344 additions and 3 deletions
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
import React, { useContext, useState, useEffect } from 'react'; |
||||
import { Typography, Input } from 'antd'; |
||||
|
||||
import { ServerStatusContext } from '../../../utils/server-status-context'; |
||||
import { TEXTFIELD_DEFAULTS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants'; |
||||
|
||||
const { Title } = Typography; |
||||
|
||||
export default function EditSocialLinks() { |
||||
const [newTagInput, setNewTagInput] = useState(''); |
||||
const [submitStatus, setSubmitStatus] = useState(null); |
||||
const [submitStatusMessage, setSubmitStatusMessage] = useState(''); |
||||
const serverStatusData = useContext(ServerStatusContext); |
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; |
||||
|
||||
const { instanceDetails } = serverConfig; |
||||
const { tags = [] } = instanceDetails; |
||||
|
||||
const configPath = 'instanceDetails'; |
||||
|
||||
const { |
||||
apiPath, |
||||
maxLength, |
||||
placeholder, |
||||
} = TEXTFIELD_DEFAULTS[configPath].tags || {}; |
||||
|
||||
|
||||
let resetTimer = null; |
||||
|
||||
useEffect(() => { |
||||
return () => { |
||||
clearTimeout(resetTimer); |
||||
} |
||||
}, []); |
||||
|
||||
const resetStates = () => { |
||||
setSubmitStatus(null); |
||||
setSubmitStatusMessage(''); |
||||
resetTimer = null; |
||||
clearTimeout(resetTimer); |
||||
} |
||||
|
||||
// posts all the tags at once as an array obj
|
||||
const postUpdateToAPI = async (postValue: any) => { |
||||
await postConfigUpdateToAPI({ |
||||
apiPath, |
||||
data: { value: postValue }, |
||||
onSuccess: () => { |
||||
setFieldInConfigState({ fieldName: 'socialHandles', value: postValue, path: configPath }); |
||||
setSubmitStatus('success'); |
||||
setSubmitStatusMessage('Tags updated.'); |
||||
setNewTagInput(''); |
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
||||
}, |
||||
onError: (message: string) => { |
||||
setSubmitStatus('error'); |
||||
setSubmitStatusMessage(message); |
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
const handleInputChange = e => { |
||||
if (submitStatusMessage !== '') { |
||||
setSubmitStatusMessage(''); |
||||
} |
||||
setNewTagInput(e.target.value); |
||||
}; |
||||
|
||||
// send to api and do stuff
|
||||
const handleSubmitNewLink = () => { |
||||
resetStates(); |
||||
const newTag = newTagInput.trim(); |
||||
if (newTag === '') { |
||||
setSubmitStatusMessage('Please enter a tag'); |
||||
return; |
||||
} |
||||
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) { |
||||
setSubmitStatusMessage('This tag is already used!'); |
||||
return; |
||||
} |
||||
|
||||
const updatedTags = [...tags, newTag]; |
||||
postUpdateToAPI(updatedTags); |
||||
}; |
||||
|
||||
const handleDeleteLink = index => { |
||||
resetStates(); |
||||
const updatedTags = [...tags]; |
||||
updatedTags.splice(index, 1); |
||||
postUpdateToAPI(updatedTags); |
||||
} |
||||
|
||||
const { |
||||
icon: newStatusIcon = null, |
||||
message: newStatusMessage = '', |
||||
} = SUCCESS_STATES[submitStatus] || {}; |
||||
|
||||
return ( |
||||
<div className="tag-editor-container"> |
||||
|
||||
<Title level={3}>Add Tags</Title> |
||||
<p>This is a great way to categorize your Owncast server on the Directory!</p> |
||||
|
||||
<div className="tag-current-tags"> |
||||
{tags.map((tag, index) => { |
||||
const handleClose = () => { |
||||
handleDeleteLink(index); |
||||
}; |
||||
return ( |
||||
<Tag closable onClose={handleClose} key={`tag-${tag}-${index}`}>{tag}</Tag> |
||||
); |
||||
})} |
||||
</div> |
||||
<div className={`status-message ${submitStatus || ''}`}> |
||||
{newStatusIcon} {newStatusMessage} {submitStatusMessage} |
||||
</div> |
||||
<div className="add-new-tag-section"> |
||||
<Input |
||||
type="text" |
||||
className="new-tag-input" |
||||
value={newTagInput} |
||||
onChange={handleInputChange} |
||||
onPressEnter={handleSubmitNewTag} |
||||
maxLength={maxLength} |
||||
placeholder={placeholder} |
||||
allowClear |
||||
/> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react'; |
||||
import { PlusOutlined } from "@ant-design/icons"; |
||||
import { Select, Divider, Input } from "antd"; |
||||
import classNames from 'classnames'; |
||||
import { SocialHandleItem } from "../../../types/config-section"; |
||||
import { NEXT_PUBLIC_API_HOST } from '../../../utils/apis'; |
||||
|
||||
|
||||
interface DropdownProps { |
||||
iconList: SocialHandleItem[]; |
||||
selectedOption?: string; |
||||
} |
||||
interface DropdownOptionProps extends SocialHandleItem { |
||||
isSelected: boolean; |
||||
} |
||||
|
||||
// Add "Other" item which creates a text field
|
||||
// Add fixed custom ones - "url", "donate", "follow", "rss"
|
||||
|
||||
function dropdownRender(menu) { |
||||
console.log({menu}) |
||||
return 'hi'; |
||||
} |
||||
|
||||
export default function SocialDropdown({ iconList, selectedOption }: DropdownProps) { |
||||
const [name, setName] = useState(''); |
||||
|
||||
const handleNameChange = event => { |
||||
setName(event.target.value); |
||||
}; |
||||
|
||||
const handleAddItem = () => { |
||||
console.log('addItem'); |
||||
// const { items, name } = this.state;
|
||||
// this.setState({
|
||||
// items: [...items, name || `New item ${index++}`],
|
||||
// name: '',
|
||||
// });
|
||||
}; |
||||
|
||||
|
||||
return ( |
||||
<div className="social-dropdown-container"> |
||||
<Select |
||||
style={{ width: 240 }} |
||||
className="social-dropdown" |
||||
placeholder="Social platform..." |
||||
// defaultValue
|
||||
dropdownRender={menu => ( |
||||
<> |
||||
{menu} |
||||
<Divider style={{ margin: '4px 0' }} /> |
||||
<div style={{ display: 'flex', flexWrap: 'nowrap', padding: 8 }}> |
||||
<Input style={{ flex: 'auto' }} value="" onChange={handleNameChange} /> |
||||
<a |
||||
style={{ flex: 'none', padding: '8px', display: 'block', cursor: 'pointer' }} |
||||
onClick={handleAddItem} |
||||
> |
||||
<PlusOutlined /> Add item |
||||
</a> |
||||
</div> |
||||
</> |
||||
)} |
||||
> |
||||
{iconList.map(item => { |
||||
const { platform, icon, key } = item; |
||||
return ( |
||||
<Select.Option className="social-option" key={`platform-${key}`} value={key}> |
||||
<span className="option-icon"> |
||||
<img src={`${NEXT_PUBLIC_API_HOST}${icon}`} alt="" className="option-icon" /> |
||||
</span> |
||||
<span className="option-label">{platform}</span> |
||||
</Select.Option> |
||||
); |
||||
}) |
||||
} |
||||
</Select> |
||||
|
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
import React, { useState, useContext, useEffect } from 'react'; |
||||
import { Typography } from 'antd'; |
||||
import SocialDropdown from './components/config/social-icons-dropdown'; |
||||
import { fetchData, SOCIAL_PLATFORMS_LIST } from '../utils/apis'; |
||||
import { ServerStatusContext } from '../utils/server-status-context'; |
||||
|
||||
const { Title } = Typography; |
||||
|
||||
|
||||
// get icons
|
||||
|
||||
export default function ConfigSocialLinks() { |
||||
const [availableIconsList, setAvailableIconsList] = useState([]); |
||||
const [currentSocialHandles, setCurrentSocialHandles] = useState([]); |
||||
|
||||
const serverStatusData = useContext(ServerStatusContext); |
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; |
||||
|
||||
const { instanceDetails } = serverConfig; |
||||
const { socialHandles: initialSocialHandles } = instanceDetails; |
||||
|
||||
const getAvailableIcons = async () => { |
||||
try { |
||||
const result = await fetchData(SOCIAL_PLATFORMS_LIST, { auth: false }); |
||||
const list = Object.keys(result).map(item => ({ |
||||
key: item, |
||||
...result[item], |
||||
})); |
||||
console.log({result}) |
||||
setAvailableIconsList(list); |
||||
|
||||
} catch (error) { |
||||
console.log(error) |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
getAvailableIcons(); |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
setCurrentSocialHandles(initialSocialHandles); |
||||
}, [instanceDetails]); |
||||
|
||||
|
||||
|
||||
return ( |
||||
<div className="config-social-links"> |
||||
<Title level={2}>Social Links</Title> |
||||
<p>Add all your social media handles and links to your other profiles here.</p> |
||||
|
||||
<SocialDropdown iconList={availableIconsList} /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
Loading…
Reference in new issue