mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-16 13:01:04 +02:00
Add twitter forwarder frontend (#2038)
* Got basic add twitter forwarder account working * Got delete twitter account working * Link to profile page from twitter account card * Simplify text for delete twitter account button * Update frontend build
This commit is contained in:
parent
b578ada984
commit
c3930b8fe0
33 changed files with 872 additions and 98 deletions
|
|
@ -12,6 +12,7 @@ import Payments from './components/Payments'
|
|||
import Peers from './components/Peers'
|
||||
import Feed from './components/Feed'
|
||||
import Search from './components/Search'
|
||||
import Twitter from './components/Twitter'
|
||||
import Notifications from './components/Notifications'
|
||||
import Alerts from './components/Alerts'
|
||||
|
||||
|
|
@ -50,6 +51,9 @@ const DefaultContainer = withRouter(({ history }) => {
|
|||
<Route path="/app/peers" exact>
|
||||
<Peers/>
|
||||
</Route>
|
||||
<Route path="/app/twitter" exact>
|
||||
<Twitter/>
|
||||
</Route>
|
||||
<Route path="/app/notifications" exact>
|
||||
<Notifications/>
|
||||
</Route>
|
||||
|
|
|
|||
|
|
@ -744,3 +744,40 @@ export const downloadSqueak = (squeakHash) => {
|
|||
deser: deser,
|
||||
});
|
||||
}
|
||||
|
||||
export const getTwitterAccounts = () => {
|
||||
console.log('Calling getTwitterAccounts');
|
||||
const request = new GetTwitterAccountsRequest();
|
||||
const deser = GetTwitterAccountsReply.deserializeBinary;
|
||||
return baseRequest({
|
||||
url: '/gettwitteraccounts',
|
||||
req: request,
|
||||
deser: deser,
|
||||
});
|
||||
}
|
||||
|
||||
export const createTwitterAccount = (twitterHandle, profileId, bearerToken) => {
|
||||
console.log('Calling createTwitterAccount');
|
||||
const request = new AddTwitterAccountRequest();
|
||||
request.setHandle(twitterHandle);
|
||||
request.setProfileId(profileId);
|
||||
request.setBearerToken(bearerToken);
|
||||
const deser = AddTwitterAccountReply.deserializeBinary;
|
||||
return baseRequest({
|
||||
url: '/addtwitteraccount',
|
||||
req: request,
|
||||
deser: deser,
|
||||
});
|
||||
}
|
||||
|
||||
export const deleteTwitterAccount = (twitterAccountId) => {
|
||||
console.log('Calling deleteTwitterAccount');
|
||||
const request = new DeleteTwitterAccountRequest();
|
||||
request.setTwitterAccountId(twitterAccountId);
|
||||
const deser = DeleteTwitterAccountReply.deserializeBinary;
|
||||
return baseRequest({
|
||||
url: '/deletetwitteraccount',
|
||||
req: request,
|
||||
deser: deser,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
175
frontend/src/components/Twitter/index.js
Normal file
175
frontend/src/components/Twitter/index.js
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import React, { useEffect, useState, useContext } from 'react'
|
||||
import './style.scss'
|
||||
import { withRouter, Link } from 'react-router-dom'
|
||||
import { ICON_SEARCH, ICON_ARROWBACK, ICON_CLOSE } from '../../Icons'
|
||||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
import Loader from '../Loader'
|
||||
import SqueakCard from '../SqueakCard'
|
||||
import Select from 'react-select'
|
||||
|
||||
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import TwitterAccounts from '../../features/twitterAccounts/TwitterAccounts'
|
||||
import {
|
||||
setCreateSigningProfile,
|
||||
} from '../../features/profiles/profilesSlice'
|
||||
|
||||
import {
|
||||
setCreateTwitterAccount,
|
||||
} from '../../features/twitterAccounts/twitterAccountsSlice'
|
||||
|
||||
import {
|
||||
selectSigningProfiles,
|
||||
fetchSigningProfiles,
|
||||
} from '../../features/profiles/profilesSlice'
|
||||
|
||||
|
||||
const Twitter = (props) => {
|
||||
const [tab, setTab] = useState('Signing Profiles')
|
||||
const [twitterAccountModalOpen, setTwitterAccountModalOpen] = useState(false)
|
||||
const [styleBody, setStyleBody] = useState(false)
|
||||
const [twitterHandle, setTwitterHandle] = useState('')
|
||||
const [newProfilePubkey, setNewProfilePubkey] = useState('')
|
||||
const [bearerToken, setBearerToken] = useState('')
|
||||
const [signingProfile, setSigningProfile] = useState(null)
|
||||
|
||||
const signingProfiles = useSelector(selectSigningProfiles);
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const searchOnChange = (param) => {
|
||||
if(tab !== 'Search'){setTab('Search')}
|
||||
if(param.length>0){
|
||||
// TODO: search for a profile by name.
|
||||
}
|
||||
}
|
||||
|
||||
const optionsFromProfiles = (profiles) => {
|
||||
return profiles.map((p) => {
|
||||
return { value: p, label: p.getProfileName() }
|
||||
// return { value: 'chocolate', label: 'Chocolate' }
|
||||
});
|
||||
}
|
||||
|
||||
const toggleCreateTwitterAccountModal = (param, type) => {
|
||||
setStyleBody(!styleBody)
|
||||
setTimeout(()=>{ setTwitterAccountModalOpen(!twitterAccountModalOpen) },20)
|
||||
}
|
||||
|
||||
const createTwitterAccount = () => {
|
||||
console.log('Create twitter account with handle:', twitterHandle);
|
||||
if (!signingProfile) {
|
||||
alert('Signing profile must be set.');
|
||||
return;
|
||||
}
|
||||
dispatch(setCreateTwitterAccount({
|
||||
twitterHandle: twitterHandle,
|
||||
profileId: signingProfile.getProfileId(),
|
||||
bearerToken: bearerToken,
|
||||
}))
|
||||
.then(unwrapResult)
|
||||
.then((pubkey) => {
|
||||
console.log('Created twitter account with handle', twitterHandle);
|
||||
})
|
||||
.catch((err) => {
|
||||
alert(err.message);
|
||||
});
|
||||
toggleCreateTwitterAccountModal();
|
||||
}
|
||||
|
||||
const handleModalClick = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleChangeSigningProfile = (e) => {
|
||||
setSigningProfile(e.value);
|
||||
}
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
|
||||
<div className="explore-wrapper">
|
||||
<div className="explore-header">
|
||||
<div className="explore-search-wrapper">
|
||||
<div className="explore-search-icon">
|
||||
<ICON_SEARCH/>
|
||||
</div>
|
||||
<div className="explore-search-input">
|
||||
<input onChange={(e)=>searchOnChange(e.target.value)} placeholder="Search for people" type="text" name="search"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="profile-details-wrapper">
|
||||
<div className="profiles-options">
|
||||
<div onClick={(e)=>toggleCreateTwitterAccountModal('edit')}
|
||||
className='profiles-create-button'>
|
||||
<span>Add Twitter Account</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="explore-nav-menu">
|
||||
<div onClick={()=>setTab('Signing Profiles')} className={tab === 'Signing Profiles' ? `explore-nav-item activeTab` : `explore-nav-item`}>
|
||||
Twitter Accounts
|
||||
</div>
|
||||
</div>
|
||||
<TwitterAccounts />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Modal for create signing profile */}
|
||||
<div onClick={()=>toggleCreateTwitterAccountModal()} style={{display: twitterAccountModalOpen ? 'block' : 'none'}} className="modal-edit">
|
||||
<div onClick={(e)=>handleModalClick(e)} className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-closeIcon">
|
||||
<div onClick={()=>toggleCreateTwitterAccountModal()} className="modal-closeIcon-wrap">
|
||||
<ICON_CLOSE />
|
||||
</div>
|
||||
</div>
|
||||
<p className="modal-title">Add Twitter Account</p>
|
||||
|
||||
<div className="save-modal-wrapper">
|
||||
<div onClick={createTwitterAccount} className="save-modal-btn">
|
||||
Submit
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<form className="edit-form">
|
||||
<div className="edit-input-wrap">
|
||||
<div className="edit-input-content">
|
||||
<label>Twitter Handle</label>
|
||||
<input onChange={(e)=>setTwitterHandle(e.target.value)} type="text" name="name" className="edit-input"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="edit-input-wrap">
|
||||
<div className="edit-input-content">
|
||||
<label>Twitter Bearer Token</label>
|
||||
<input onChange={(e)=>setBearerToken(e.target.value)} type="text" name="name" className="edit-input"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="edit-input-wrap">
|
||||
<div className="edit-input-content">
|
||||
<label>Signing Profile</label>
|
||||
<div className="inner-input-box">
|
||||
<Select options={optionsFromProfiles(signingProfiles)} onChange={handleChangeSigningProfile} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(Twitter)
|
||||
298
frontend/src/components/Twitter/style.scss
Normal file
298
frontend/src/components/Twitter/style.scss
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
.explore-wrapper{
|
||||
max-width: 600px;
|
||||
border-right: 1px solid rgb(230, 236, 240);
|
||||
width: 100%;
|
||||
// height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 2000px;
|
||||
}
|
||||
|
||||
|
||||
.explore-header{
|
||||
position: sticky;
|
||||
border-left: 1px solid rgb(230, 236, 240);
|
||||
background-color: #fff;
|
||||
z-index: 8;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 53px;
|
||||
min-height: 53px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-border{
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
}
|
||||
|
||||
.explore-search-wrapper{
|
||||
background-color: rgb(230, 236, 240);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 38px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.explore-search-icon{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.explore-search-icon svg{
|
||||
width: 40px;
|
||||
height: 18.75px;
|
||||
fill: rgb(101, 119, 134);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.explore-search-input{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.explore-search-input input{
|
||||
background-color: inherit;
|
||||
border: inherit;
|
||||
padding: 6px 10px;
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
color: rgb(101, 119, 134);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.explore-nav-menu{
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
}
|
||||
|
||||
.explore-nav-item{
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
color: rgb(101, 119, 134);
|
||||
transition: 0.2s;
|
||||
will-change: background-color;
|
||||
box-sizing:border-box;
|
||||
border-bottom: 2px solid transparent;
|
||||
&:hover{
|
||||
background-color: rgba(29, 161, 242, 0.1);
|
||||
color: rgb(29, 161, 242);
|
||||
}
|
||||
}
|
||||
|
||||
.activeTab{
|
||||
border-bottom: 2px solid rgb(29, 161, 242);
|
||||
color: rgb(29, 161, 242);
|
||||
}
|
||||
|
||||
.search-result-wapper{
|
||||
border-bottom: 1px solid rgb(230, 236, 240);
|
||||
padding: 10px 15px;
|
||||
transition: 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
&:hover{
|
||||
background-color: rgb(245,248,250);
|
||||
}
|
||||
}
|
||||
|
||||
.search-userPic-wrapper{
|
||||
flex-basis: 49px;
|
||||
margin-right: 10px;
|
||||
img{
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.search-user-details{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-user-info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-user-name{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-user-username{
|
||||
color: rgb(101, 119, 134);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.search-user-bio{
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.search-user-warp{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.follow-btn-wrap{
|
||||
min-height: 30px;
|
||||
min-width: 70px;
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: 1px solid #1da1f2;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
&:hover{
|
||||
background-color: rgba(29, 161, 242, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.follow-btn-wrap span{
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
color: #1da1f2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.trending-card-wrapper{
|
||||
border-bottom: 1px solid rgb(245,248,250);
|
||||
padding: 10px 15px;
|
||||
transition: 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&:hover{
|
||||
background-color: rgb(245, 248, 250);
|
||||
}
|
||||
}
|
||||
|
||||
.trending-card-header{
|
||||
color: rgb(101, 119, 134);
|
||||
font-size: 14px;
|
||||
span{
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.trending-card-content{
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.trending-card-count{
|
||||
font-size: 15px;
|
||||
color: rgb(101, 119, 134);
|
||||
}
|
||||
|
||||
.try-searching{
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
color: #657786;
|
||||
div{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.unfollow-switch{
|
||||
background-color: rgb(29, 161, 242);
|
||||
span{color: #fff !important;}
|
||||
}
|
||||
|
||||
.unfollow-switch:hover{
|
||||
background-color: rgb(202,32,85) !important;
|
||||
border: 1px solid transparent;
|
||||
span{
|
||||
color: #fff;
|
||||
span{display: none;}
|
||||
&:before{
|
||||
content: 'Unfollow';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-header-back{
|
||||
min-width: 55px;
|
||||
min-height: 30px;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.explore-back-wrapper{
|
||||
margin-left: -5px;
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
transition: 0.2s ease-in-out;
|
||||
will-change: background-color;
|
||||
border: 1px solid rgba(0, 0, 0, 0);
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.explore-back-wrapper svg{
|
||||
height: 1.5em;
|
||||
fill: rgb(29,161,242);
|
||||
}
|
||||
.explore-back-wrapper:hover{
|
||||
background-color: rgba(29,161,242,0.1);
|
||||
}
|
||||
|
||||
.profiles-details-wrapper{
|
||||
padding: 10px 15px 15px 15px;
|
||||
}
|
||||
|
||||
.profiles-options{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.profiles-create-button{
|
||||
min-height: 39px;
|
||||
min-width: 98.8px;
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgb(29, 161, 242);
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 7px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
span{
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
color: rgb(29, 161, 242);
|
||||
width: 100%
|
||||
}
|
||||
&:hover{
|
||||
background-color: rgba(29, 161, 242,0.1);
|
||||
}
|
||||
}
|
||||
98
frontend/src/features/twitterAccounts/TwitterAccountCard.js
Normal file
98
frontend/src/features/twitterAccounts/TwitterAccountCard.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { withRouter, Link } from 'react-router-dom'
|
||||
import moment from 'moment'
|
||||
import { ICON_SETTINGS } from '../../Icons'
|
||||
import { getProfileImageSrcString } from '../../squeakimages/images';
|
||||
|
||||
|
||||
import SqueakCard from '../../components/SqueakCard'
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
import {
|
||||
setDeleteTwitterAccount,
|
||||
} from './twitterAccountsSlice'
|
||||
|
||||
|
||||
const TwitterAccountCard = (props) => {
|
||||
const [moreMenu, setMoreMenu] = useState(false)
|
||||
const [styleBody, setStyleBody] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
|
||||
|
||||
const twitterAccount = props.twitterAccount;
|
||||
const profile = twitterAccount.getProfile();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// const followUser = (e, id) => {
|
||||
// e.stopPropagation()
|
||||
// console.log('Follow clicked');
|
||||
// dispatch(setFollowProfile(id));
|
||||
// }
|
||||
//
|
||||
// const unfollowUser = (e,id) => {
|
||||
// e.stopPropagation()
|
||||
// console.log('Unfollow clicked');
|
||||
// dispatch(setUnfollowProfile(id));
|
||||
// }
|
||||
|
||||
const deleteTwitterAccount = (e) => {
|
||||
e.stopPropagation()
|
||||
console.log('Delete clicked');
|
||||
dispatch(setDeleteTwitterAccount({
|
||||
twitterAccountId: twitterAccount.getTwitterAccountId(),
|
||||
}));
|
||||
}
|
||||
|
||||
const openMore = () => { setMoreMenu(!moreMenu) }
|
||||
|
||||
const handleMenuClick = (e) => { e.stopPropagation() }
|
||||
|
||||
// const toggleDeleteModal = () => {
|
||||
// setStyleBody(!styleBody)
|
||||
// setSaved(false)
|
||||
// setTimeout(()=>{ setDeleteModalOpen(!deleteModalOpen) },20)
|
||||
// }
|
||||
|
||||
return <div onClick={(e)=>e.stopPropagation()} key={profile.getPubkey()} className="search-result-wapper">
|
||||
<Link to={`/app/profile/${profile.getPubkey()}`} className="search-userPic-wrapper">
|
||||
<img style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={`${getProfileImageSrcString(profile)}`}/>
|
||||
</Link>
|
||||
<div className="search-user-details">
|
||||
<div className="search-user-warp">
|
||||
<div className="search-user-info">
|
||||
<div className="search-user-name">{profile.getProfileName()}</div>
|
||||
<div className="search-user-username">@{profile.getPubkey()}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-user-username"><b>Twitter Handle</b>: <a href={`https://twitter.com/${twitterAccount.getHandle()}`} style={{color: "blue", fontWeight: 'bold'}}>
|
||||
{twitterAccount.getHandle()}
|
||||
</a></div>
|
||||
<div className="search-user-bio">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="profileMoreMenu" onClick={openMore} className="Nav-link">
|
||||
<div className={"Nav-item-hover"}>
|
||||
<ICON_SETTINGS />
|
||||
</div>
|
||||
<div onClick={()=>openMore()} style={{display: moreMenu ? 'block' : 'none'}} className="more-menu-background">
|
||||
<div className="more-modal-wrapper">
|
||||
{moreMenu ?
|
||||
<div style={{
|
||||
top: document.getElementById('profileMoreMenu') && `${document.getElementById('profileMoreMenu').getBoundingClientRect().top - 40}px`,
|
||||
left: document.getElementById('profileMoreMenu') && `${document.getElementById('profileMoreMenu').getBoundingClientRect().left}px`,
|
||||
height: '40px',
|
||||
}} onClick={(e)=>handleMenuClick(e)} className="more-menu-content">
|
||||
<div onClick={deleteTwitterAccount} className="more-menu-item">
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default withRouter(TwitterAccountCard)
|
||||
40
frontend/src/features/twitterAccounts/TwitterAccounts.js
Normal file
40
frontend/src/features/twitterAccounts/TwitterAccounts.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { withRouter, Link } from 'react-router-dom'
|
||||
import moment from 'moment'
|
||||
|
||||
import TwitterAccountCard from './TwitterAccountCard'
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
|
||||
import {
|
||||
fetchTwitterAccounts,
|
||||
clearTwitterAccounts,
|
||||
selectTwitterAccounts,
|
||||
selectTwitterAccountsStatus,
|
||||
} from './twitterAccountsSlice'
|
||||
|
||||
|
||||
const TwitterAccounts = (props) => {
|
||||
const twitterAccounts = useSelector(selectTwitterAccounts);
|
||||
const twitterAccountsStatus = useSelector(selectTwitterAccountsStatus);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0)
|
||||
console.log('fetchTwitterAccounts');
|
||||
dispatch(clearTwitterAccounts());
|
||||
dispatch(fetchTwitterAccounts(null));
|
||||
}, [])
|
||||
|
||||
const renderedListItems = twitterAccounts.map(twitterAccount=>{
|
||||
return <TwitterAccountCard twitterAccount={twitterAccount}/>
|
||||
})
|
||||
|
||||
return <>
|
||||
{renderedListItems}
|
||||
</>
|
||||
}
|
||||
|
||||
export default withRouter(TwitterAccounts)
|
||||
120
frontend/src/features/twitterAccounts/twitterAccountsSlice.js
Normal file
120
frontend/src/features/twitterAccounts/twitterAccountsSlice.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import {
|
||||
createSlice,
|
||||
createSelector,
|
||||
createAsyncThunk,
|
||||
createEntityAdapter,
|
||||
} from '@reduxjs/toolkit'
|
||||
import {
|
||||
getTwitterAccounts,
|
||||
createTwitterAccount,
|
||||
deleteTwitterAccount,
|
||||
} from '../../api/client'
|
||||
|
||||
const initialState = {
|
||||
twitterAccountsStatus: 'idle',
|
||||
twitterAccounts: [],
|
||||
createTwitterAccountStatus: 'idle',
|
||||
}
|
||||
|
||||
|
||||
export const setDeleteTwitterAccount = createAsyncThunk(
|
||||
'twitterAccounts/setDeleteTwitterAccount',
|
||||
async (values) => {
|
||||
console.log('Deleting twitter account');
|
||||
let twitterAccountId = values.twitterAccountId;
|
||||
await deleteTwitterAccount(twitterAccountId);
|
||||
const response = await getTwitterAccounts();
|
||||
console.log(response);
|
||||
return response.getTwitterAccountsList();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchTwitterAccounts = createAsyncThunk(
|
||||
'twitterAccounts/fetchTwitterAccounts',
|
||||
async () => {
|
||||
const response = await getTwitterAccounts();
|
||||
console.log(response);
|
||||
return response.getTwitterAccountsList();
|
||||
}
|
||||
)
|
||||
|
||||
export const setCreateTwitterAccount = createAsyncThunk(
|
||||
'twitterAccounts/setCreateTwitterAccount',
|
||||
async (values) => {
|
||||
console.log('Creating twitter account');
|
||||
let twitterHandle = values.twitterHandle;
|
||||
let profileId = values.profileId;
|
||||
let bearerToken = values.bearerToken;
|
||||
const createResponse = await createTwitterAccount(
|
||||
twitterHandle,
|
||||
profileId,
|
||||
bearerToken,
|
||||
);
|
||||
console.log(createResponse);
|
||||
const response = await getTwitterAccounts();
|
||||
console.log(response);
|
||||
return response.getTwitterAccountsList();
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// const updatedProfileInArray = (profileArr, newProfile) => {
|
||||
// const currentIndex = profileArr.findIndex(profile => profile.getPubkey() === newProfile.getPubkey());
|
||||
// if (currentIndex != -1) {
|
||||
// profileArr[currentIndex] = newProfile;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
const twitterAccountsSlice = createSlice({
|
||||
name: 'twitterAccounts',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearTwitterAccounts(state, action) {
|
||||
state.createTwitterAccountStatus = 'idle'
|
||||
state.twitterAccounts = [];
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchTwitterAccounts.pending, (state, action) => {
|
||||
state.twitterAccountsStatus = 'loading'
|
||||
})
|
||||
.addCase(fetchTwitterAccounts.fulfilled, (state, action) => {
|
||||
const newTwitterAccounts = action.payload;
|
||||
state.twitterAccounts = newTwitterAccounts;
|
||||
state.twitterAccountsStatus = 'idle'
|
||||
})
|
||||
.addCase(setCreateTwitterAccount.pending, (state, action) => {
|
||||
console.log('setCreateTwitterAccount pending');
|
||||
state.createTwitterAccountStatus = 'loading'
|
||||
})
|
||||
.addCase(setCreateTwitterAccount.fulfilled, (state, action) => {
|
||||
console.log('setCreateTwitterAccount fulfilled');
|
||||
console.log(action);
|
||||
const newTwitterAccounts = action.payload;
|
||||
state.twitterAccounts = newTwitterAccounts;
|
||||
state.twitterAccountsStatus = 'idle'
|
||||
state.createTwitterAccountStatus = 'idle';
|
||||
})
|
||||
.addCase(setDeleteTwitterAccount.fulfilled, (state, action) => {
|
||||
console.log(action);
|
||||
const newTwitterAccounts = action.payload;
|
||||
state.twitterAccounts = newTwitterAccounts;
|
||||
state.twitterAccountsStatus = 'idle'
|
||||
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const {
|
||||
clearTwitterAccounts,
|
||||
} = twitterAccountsSlice.actions
|
||||
|
||||
export default twitterAccountsSlice.reducer
|
||||
|
||||
export const selectTwitterAccountsStatus = state => state.twitterAccounts.twitterAccountsStatus
|
||||
|
||||
export const selectTwitterAccounts = state => state.twitterAccounts.twitterAccounts
|
||||
|
||||
export const selectCreateTwitterAccountStatus = state => state.twitterAccounts.createTwitterAccountStatus
|
||||
|
|
@ -8,6 +8,7 @@ import externalAddressReducer from './features/externalAddress/externalAddressSl
|
|||
import paymentsReducer from './features/payments/paymentsSlice'
|
||||
import sellPriceReducer from './features/sellPrice/sellPriceSlice'
|
||||
import accountReducer from './features/account/accountSlice'
|
||||
import twitterAccounsReducer from './features/twitterAccounts/twitterAccountsSlice'
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
|
|
@ -19,6 +20,7 @@ const store = configureStore({
|
|||
payments: paymentsReducer,
|
||||
sellPrice: sellPriceReducer,
|
||||
account: accountReducer,
|
||||
twitterAccounts: twitterAccounsReducer,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue