diff --git a/app/ResolveRoute.js b/app/ResolveRoute.js
index d690921b..d55071d8 100644
--- a/app/ResolveRoute.js
+++ b/app/ResolveRoute.js
@@ -1,9 +1,9 @@
export const routeRegex = {
PostsIndex: /^\/(@[\w\.\d-]+)\/feed\/?$/,
UserProfile1: /^\/(@[\w\.\d-]+)\/?$/,
- UserProfile2: /^\/(@[\w\.\d-]+)\/(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|sponsors|settings)\/??(?:&?[^=&]*=[^=&]*)*$/,
+ UserProfile2: /^\/(@[\w\.\d-]+)\/(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|sponsors|referrals|settings)\/??(?:&?[^=&]*=[^=&]*)*$/,
UserProfile3: /^\/(@[\w\.\d-]+)\/[\w\.\d-]+/,
- UserEndPoints: /^(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|sponsors|settings)$/,
+ UserEndPoints: /^(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|sponsors|referrals|settings)$/,
CategoryFilters: /^\/(hot|responses|donates|forums|trending|promoted|allposts|allcomments|created|active)\/?$/ig,
PostNoCategory: /^\/(@[\w\.\d-]+)\/([\w\d-]+)/,
Post: /^\/([\w\d\-\/]+)\/(\@[\w\d\.-]+)\/([\w\d-]+)\/?($|\?)/,
@@ -44,6 +44,9 @@ export default function resolveRoute(path)
if (path === '/minused_accounts') {
return {page: 'MinusedAccounts'};
}
+ if (path === '/referrers') {
+ return {page: 'Referrers'};
+ }
if (process.env.IS_APP) {
if (path === '/__app_goto_url') {
return {page: 'AppGotoURL'};
diff --git a/app/RootRoute.js b/app/RootRoute.js
index 649843e8..7ab82e45 100644
--- a/app/RootRoute.js
+++ b/app/RootRoute.js
@@ -40,6 +40,8 @@ export default {
cb(null, [require('@pages/TagsIndex')]);
} else if (route.page === 'MinusedAccounts') {
cb(null, [require('@pages/MinusedAccounts')]);
+ } else if (route.page === 'Referrers') {
+ cb(null, [require('@pages/Referrers')])
} else if (route.page === 'AppGotoURL') {
cb(null, [require('@pages/app/AppGotoURL')]);
} else if (route.page === 'AppSplash') {
diff --git a/app/components/all.scss b/app/components/all.scss
index aa90959f..b6ba1da3 100644
--- a/app/components/all.scss
+++ b/app/components/all.scss
@@ -72,6 +72,7 @@
@import "./modules/GiftNFT";
@import "./modules/Header";
@import "./modules/LoginForm";
+@import "./modules/Referrals";
@import "./modules/Settings";
@import "./modules/SidePanel";
@import "./modules/SignUp";
diff --git a/app/components/elements/TimeAgoWrapper.js b/app/components/elements/TimeAgoWrapper.js
index 62d8aeb8..85e11154 100644
--- a/app/components/elements/TimeAgoWrapper.js
+++ b/app/components/elements/TimeAgoWrapper.js
@@ -1,20 +1,32 @@
/* eslint react/prop-types: 0 */
import React from 'react';
-import { FormattedRelativeTime } from 'react-intl'
+import { FormattedRelativeTime, formatRelativeTime } from 'react-intl'
import { selectUnit } from 'app/utils/selectUnit'
import Tooltip from 'app/components/elements/Tooltip'
+function processDate(date) {
+ if (date && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$/.test(date)) {
+ date = date + 'Z' // Firefox really wants this Z (Zulu)
+ }
+ const dt = new Date(date)
+ const res = selectUnit(dt)
+ return { dt, res }
+}
+
+export function wrapDate(date, intl) {
+ const { dt, res } = processDate(date)
+ const { value, unit } = res
+ return intl.formatRelativeTime(value, unit)
+}
+
export default class TimeAgoWrapper extends React.Component {
render() {
- let {date, className} = this.props
- if (date && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$/.test(date)) {
- date = date + 'Z' // Firefox really wants this Z (Zulu)
- }
- const dt = new Date(date)
- const { value, unit } = selectUnit(dt)
+ const { date, className } = this.props
+ const { dt, res } = processDate(date)
+ const { value, unit } = res
return
-
-
+
+
}
}
diff --git a/app/components/modules/Header.jsx b/app/components/modules/Header.jsx
index ebc946f3..a4222088 100644
--- a/app/components/modules/Header.jsx
+++ b/app/components/modules/Header.jsx
@@ -125,6 +125,12 @@ class Header extends React.Component {
if(route.params[1] === "followed"){
page_title = tt('header_jsx.people_followed_by') + " " + user_title;
}
+ if (route.params[1] === "sponsors"){
+ page_title = tt('sponsors_jsx.your_sponsors') + " " + user_title
+ }
+ if (route.params[1] === "referrals"){
+ page_title = tt('referrals_jsx.title') + " " + user_title
+ }
if(route.params[1] === "curation-rewards"){
page_title = tt('header_jsx.curation_rewards_by') + " " + user_title;
}
diff --git a/app/components/modules/Referrals.jsx b/app/components/modules/Referrals.jsx
new file mode 100644
index 00000000..718831d9
--- /dev/null
+++ b/app/components/modules/Referrals.jsx
@@ -0,0 +1,194 @@
+import React from 'react'
+import { Link } from 'react-router'
+import { connect } from 'react-redux'
+import tt from 'counterpart'
+import { Asset } from 'golos-lib-js/lib/utils'
+import CopyToClipboard from 'react-copy-to-clipboard'
+import { IntlContext } from 'react-intl'
+
+import DateJoinWrapper from 'app/components/elements/DateJoinWrapper'
+import DropdownMenu from 'app/components/elements/DropdownMenu'
+import Icon from 'app/components/elements/Icon'
+import LoadingIndicator from 'app/components/elements/LoadingIndicator'
+import TimeAgoWrapper, { wrapDate } from 'app/components/elements/TimeAgoWrapper'
+import g from 'app/redux/GlobalReducer'
+import LinkEx from 'app/utils/LinkEx'
+import { getLastSeen } from 'app/utils/NormalizeProfile'
+import { vestsToSteem } from 'app/utils/StateFunctions'
+
+class Referrals extends React.Component {
+ static contextType = IntlContext
+
+ constructor(props, context) {
+ super(props, context)
+ }
+
+ state = {
+ }
+
+ componentDidMount() {
+ this.refetch()
+ }
+
+ refetch = () => {
+ setTimeout(() => {
+ this.props.fetchReferrals(this.props.account, '', this.sort)
+ }, 1000)
+ }
+
+ sortOrder = (e, sort) => {
+ e.preventDefault()
+ this.sort = sort
+ this.refetch()
+ }
+
+ render() {
+ const { referrals, account } = this.props
+
+ const props = this.props.props ? this.props.props.toJS() : {}
+
+ if (!referrals || !referrals.get('loaded')){
+ return
+
{tt('referrals_jsx.title')}
+
+
+ }
+
+ let refs = referrals.get('data').toJS()
+
+ let count = 0
+
+ let items = refs.map(ref => {
+ ++count
+
+ const { accounts } = this.props
+ const acc = accounts.toJS()[ref.account]
+ let lastSeen
+ if (acc) {
+ lastSeen = getLastSeen(acc)
+ }
+
+ const golosRewards = Asset(ref.referrer_rewards).plus(Asset(ref.referrer_donate_rewards))
+
+ const inactive = acc && acc.referral_end_date.startsWith('19')
+
+ const tdClass = inactive && 'inactive'
+
+ return
+
+
+ {ref.account}
+
+ |
+ {acc &&
+ {Asset(vestsToSteem(acc.vesting_shares, props) + ' GOLOS').floatString}
+ | }
+ {acc &&
+ {tt('user_profile.post_count', {count: acc.post_count || 0})}
+ | }
+
+
+ {' +'}
+ {golosRewards.floatString}
+
+ {ref.referrer_donate_rewards_uia ?
+ {' + ' + ref.referrer_donate_rewards_uia + ' UIA'} : null}
+ |
+
+
+ |
+
+ {lastSeen && }
+ |
+
+ })
+
+ let refUrl
+ if (account) {
+ refUrl = 'https://' + $STM_Config.site_domain + '/welcome?invite=' + account.name
+ }
+
+ const next_start_name = referrals.get('next_start_name')
+
+ const sortItems = [
+ { link: '#', onClick: e => {
+ this.sortOrder(e, 'by_joined', false)
+ }, value: tt('referrals_jsx.by_joined') },
+ { link: '#', onClick: e => {
+ this.sortOrder(e, 'by_rewards', true)
+ }, value: tt('referrals_jsx.by_rewards') },
+ ]
+
+ let currentSort = tt('referrals_jsx.by_joined')
+ if (this.sort === 'by_rewards') {
+ currentSort = tt('referrals_jsx.by_rewards')
+ }
+
+ return
+
{tt('referrals_jsx.title')}
+
+ {tt('referrers_jsx.button')}
+
+
{tt('referrals_jsx.desc')}
+ {refUrl &&
+
+ {' '}{tt('g.referral_link')}{' - '}
+
+ {refUrl}
+
+
+
+
+
+
+
+
}
+
+
+
+ {currentSort}
+
+
+
+
+ {items.length ?
+
+ {tt('referrals_jsx.name')} |
+ {tt('referrals_jsx.vs')} |
+ {tt('referrals_jsx.posts')} |
+ {tt('referrals_jsx.rewards')} |
+ {tt('referrals_jsx.joined')} |
+ {tt('referrals_jsx.last')} |
+
+
+ {items}
+
: null}
+ {next_start_name ?
+
+ : null}
+
+ }
+}
+
+
+export default connect(
+ state => {
+ const referrals = state.global.get('referrals')
+ const accounts = state.global.get('accounts')
+ const props = state.global.get('props')
+
+ return {
+ referrals,
+ accounts,
+ props
+ }
+ },
+ dispatch => ({
+ fetchReferrals: (referrer, start_name, sort) => {
+ if (!referrer) return
+ dispatch(g.actions.fetchReferrals({ referrer: referrer.name, start_name, sort }))
+ },
+ })
+)(Referrals)
diff --git a/app/components/modules/Referrals.scss b/app/components/modules/Referrals.scss
new file mode 100644
index 00000000..869b9561
--- /dev/null
+++ b/app/components/modules/Referrals.scss
@@ -0,0 +1,10 @@
+.Referrals {
+ .inactive-link {
+ a {
+ color: gray;
+ }
+ }
+ .inactive {
+ opacity: 50%;
+ }
+}
diff --git a/app/components/pages/Referrers.jsx b/app/components/pages/Referrers.jsx
new file mode 100644
index 00000000..3a05e371
--- /dev/null
+++ b/app/components/pages/Referrers.jsx
@@ -0,0 +1,117 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import tt from 'counterpart'
+import { Asset } from 'golos-lib-js/lib/utils'
+
+import LoadingIndicator from 'app/components/elements/LoadingIndicator'
+import g from 'app/redux/GlobalReducer'
+import { vestsToSteem } from 'app/utils/StateFunctions'
+import LinkEx from 'app/utils/LinkEx'
+
+class Referrers extends React.Component {
+ constructor(props) {
+ super(props)
+ }
+
+ state = {
+ }
+
+ componentDidMount() {
+ this.refetch()
+ }
+
+ refetch = () => {
+ setTimeout(() => {
+ this.props.fetchReferrers('', this.sort)
+ }, 1000)
+ }
+
+ sortOrder = (e, sort) => {
+ e.preventDefault()
+ this.sort = sort
+ this.refetch()
+ }
+
+ render() {
+ const { referrers } = this.props
+
+ const props = this.props.props ? this.props.props.toJS() : {}
+
+ if (!referrers || !referrers.get('loaded')){
+ return
+
{tt('referrers_jsx.title')}
+
+
+ }
+
+ let refs = referrers.get('data').toJS()
+
+ let count = 0
+
+ let items = refs.map(ref => {
+ ++count
+
+ const golosRewards = Asset(ref.referrer_rewards).plus(Asset(ref.referrer_donate_rewards))
+
+ const tdClass = ''
+
+ return
+
+
+ {ref.account}
+
+ |
+
+ {tt('user_profile.referral_count', {count: ref.referral_count || 0})}
+ |
+
+ {tt('user_profile.post_count', {count: ref.referral_post_count || 0})}
+ |
+
+ {Asset(vestsToSteem(ref.total_referral_vesting, props) + ' GOLOS').floatString}
+ |
+
+ })
+
+ const next_start_name = referrers.get('next_start_name')
+
+ return
+
{tt('referrers_jsx.title')}
+ {items.length ?
+
+ {tt('referrals_jsx.name')} |
+ {tt('referrals_jsx.vs')} |
+ {tt('referrals_jsx.posts')} |
+ {tt('referrals_jsx.rewards')} |
+
+
+ {items}
+
: null}
+ {next_start_name ?
+
+ : null}
+
+ }
+}
+
+module.exports = {
+ path: '/referrers',
+ component: connect(
+ state => {
+ const referrers = state.global.get('referrers')
+ const props = state.global.get('props')
+
+ return {
+ referrers,
+ props
+ }
+ },
+ dispatch => ({
+ fetchReferrers: (start_name, sort) => {
+ dispatch(g.actions.fetchReferrers({ start_name, sort }))
+ },
+ })
+ )(Referrers)
+}
diff --git a/app/components/pages/UserProfile.jsx b/app/components/pages/UserProfile.jsx
index 4b958f80..19bc741e 100644
--- a/app/components/pages/UserProfile.jsx
+++ b/app/components/pages/UserProfile.jsx
@@ -14,6 +14,7 @@ import UserKeys from 'app/components/elements/UserKeys';
import Settings from 'app/components/modules/Settings';
import ReputationHistory from 'app/components/modules/ReputationHistory'
import Mentions from 'app/components/modules/Mentions'
+import Referrals from 'app/components/modules/Referrals'
import Sponsors from 'app/components/modules/Sponsors'
import UserList from 'app/components/elements/UserList';
import Follow from 'app/components/elements/Follow';
@@ -398,6 +399,11 @@ export default class UserProfile extends React.Component {
+ } else if (section === 'referrals') {
+ tab_content =
+
+
+
}
tab_content =
@@ -514,6 +520,9 @@ export default class UserProfile extends React.Component {
{tt('user_profile.sponsor_count', {count: account.sponsor_count || 0})}
{isMyAccount && }
+ {tt('user_profile.referral_count', {count: account.referral_count || 0})}
+ {isMyAccount && }
+
{location && {location}}
diff --git a/app/locales/en.json b/app/locales/en.json
index 6c910d9b..3f788136 100644
--- a/app/locales/en.json
+++ b/app/locales/en.json
@@ -384,6 +384,25 @@
"responses": "responses",
"popular": "popular"
},
+ "referrals_jsx": {
+ "title": "Referrals",
+ "joined": "Registration",
+ "last": "Last activity",
+ "vs": "Golos Power",
+ "posts": "Post count",
+ "rewards": "Rewards and donates for referrer",
+ "donates_uia": "Donates in UIA",
+ "desc": "During the year, 10%% of rewards of users (registered by your referral link or invite codes) will be arrive to your TIP-balance (donates) and Golos Power (author rewards).",
+ "name": "Name",
+ "inactive": "Referral ended",
+ "end_date": "Referral witl be end ",
+ "by_joined": "By joined",
+ "by_rewards": "By rewards"
+ },
+ "referrers_jsx": {
+ "button": "View top referrers",
+ "title": "Top referrers"
+ },
"sponsorslist_jsx": {
"payment": "Payment",
"payment_hint": "Next payment, will be claimed automatically",
@@ -524,6 +543,11 @@
"one": "1 sponsor",
"other": "%(count)s sponsors"
},
+ "referral_count": {
+ "zero": "No referrals",
+ "one": "1 referral",
+ "other": "%(count)s referrals"
+ },
"account_frozen": "Account is temporarily deactivated."
},
"plurals": {
diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json
index fdabe2b5..f7a91021 100644
--- a/app/locales/ru-RU.json
+++ b/app/locales/ru-RU.json
@@ -782,6 +782,25 @@
"sponsored_authors": "Спонсируемые авторы",
"created": "Создано"
},
+ "referrals_jsx": {
+ "title": "Рефералы",
+ "joined": "Регистрация",
+ "last": "Последняя активность",
+ "vs": "Сила Голоса",
+ "posts": "Кол-во постов",
+ "rewards": "Выплаты и донаты рефереру",
+ "donates_uia": "Донаты рефереру в UIA",
+ "desc": "В течение года 10%% от вознаграждений пользователей (зарегистрированных по вашей реферальной ссылке или инвайт-чеку) будут поступать на ваш TIP-баланс (донаты) и Силу Голоса (авторские награды).",
+ "name": "Имя",
+ "inactive": "Реферальство закончилось",
+ "end_date": "Реферальство закончится ",
+ "by_joined": "Сначала новые",
+ "by_rewards": "По наградам"
+ },
+ "referrers_jsx": {
+ "button": "Смотреть топ рефереров",
+ "title": "Топ рефереров"
+ },
"sponsorslist_jsx": {
"payment": "Платеж",
"payment_hint": "Следующий платеж, спишется автоматически",
@@ -898,6 +917,11 @@
"one": "1 спонсор",
"other": "%(count)s спонсоров"
},
+ "referral_count": {
+ "zero": "0 рефералов",
+ "one": "1 реферал",
+ "other": "%(count)s рефералов"
+ },
"account_frozen": "Аккаунт временно деактивирован."
},
"invites_jsx": {
diff --git a/app/redux/FetchDataSaga.js b/app/redux/FetchDataSaga.js
index 8ebe6e87..b03ba264 100644
--- a/app/redux/FetchDataSaga.js
+++ b/app/redux/FetchDataSaga.js
@@ -8,6 +8,7 @@ import {loadFollows, fetchFollowCount} from 'app/redux/FollowSaga';
import { getBlockings, listBlockings } from 'app/redux/BlockingSaga'
import { contentPrefs as prefs } from 'app/utils/Allowance'
import { applyEventHighlight, getContent } from 'app/redux/SagaShared'
+import user from 'app/redux/User'
import GlobalReducer from './GlobalReducer';
import constants from './constants';
import session from 'app/utils/session'
@@ -30,6 +31,8 @@ export function* fetchDataWatches () {
yield fork(watchFetchVestingDelegations);
yield fork(watchFetchUiaBalances);
yield fork(watchFetchNftTokens)
+ yield fork(watchFetchReferrals)
+ yield fork(watchFetchReferrers)
}
export function* watchGetContent() {
@@ -90,6 +93,8 @@ export function* fetchState(location_change_action) {
state.tokens = []
state.sponsors = { data: [] }
state.sponsoreds = { data: [] }
+ state.referrals = { data: [] }
+ state.referrers = { data: [] }
state.minused_accounts = {}
state.accounts = {}
state.confetti_nft_active = false
@@ -283,6 +288,10 @@ export function* fetchState(location_change_action) {
}
break
+ case 'referrals':
+ state.props = yield call([api, api.getDynamicGlobalProperties])
+ break
+
case 'blog':
default:
const blogEntries = yield call([api, api.getBlogEntriesAsync], uname, 0, 20, ['fm-'], {})
@@ -443,6 +452,8 @@ export function* fetchState(location_change_action) {
state.minused_accounts.push(operation);
}
});
+ } else if (parts[0] === 'referrers') {
+ state.props = yield call([api, api.getDynamicGlobalProperties])
} else if (Object.keys(PUBLIC_API).includes(parts[0])) {
yield call(fetchData, {payload: { order: parts[0], category : tag }})
@@ -897,3 +908,61 @@ export function* fetchNftTokens({ payload: { account, start_token_id } }) {
console.error('fetchNftTokens', err)
}
}
+
+export function* watchFetchReferrals() {
+ yield takeLatest('global/FETCH_REFERRALS', fetchReferrals)
+}
+
+export function* fetchReferrals({ payload: { referrer, start_name, sort } }) {
+ try {
+ const limit = 99
+
+ const referrals = yield call([api, api.getReferralsAsync], {
+ referrer,
+ start_name: '',
+ limit: limit + 1,
+ sort: sort || 'by_joined',
+ })
+
+ const usernames = new Set()
+ for (const referral of referrals) {
+ usernames.add(referral.account)
+ }
+
+ yield put(user.actions.getAccount({ usernames: [...usernames], }))
+
+ let next_start_name
+ if (referrals.length > limit) {
+ next_start_name = referrals.pop().account
+ }
+
+ yield put(GlobalReducer.actions.receiveReferrals({ referrals, start_name, next_start_name }))
+ } catch (err) {
+ console.error('fetchReferrals', err)
+ }
+}
+
+export function* watchFetchReferrers() {
+ yield takeLatest('global/FETCH_REFERRERS', fetchReferrers)
+}
+
+export function* fetchReferrers({ payload: { start_name, sort } }) {
+ try {
+ const limit = 99
+
+ const referrers = yield call([api, api.getReferrersAsync], {
+ start_name: '',
+ limit: limit + 1,
+ sort: sort || 'by_referral_count',
+ })
+
+ let next_start_name
+ if (referrers.length > limit) {
+ next_start_name = referrers.pop().account
+ }
+
+ yield put(GlobalReducer.actions.receiveReferrers({ referrers, start_name, next_start_name }))
+ } catch (err) {
+ console.error('fetchReferrers', err)
+ }
+}
diff --git a/app/redux/GlobalReducer.js b/app/redux/GlobalReducer.js
index 6317daa9..858432e8 100644
--- a/app/redux/GlobalReducer.js
+++ b/app/redux/GlobalReducer.js
@@ -72,6 +72,10 @@ export default createModule({
res = res.delete('pso')
}
res = res.setIn(['sponsoreds', 'data'], List())
+ res = res.setIn(['referrals', 'data'], List())
+ res = res.setIn(['referrals', 'loaded'], false)
+ res = res.setIn(['referrers', 'data'], List())
+ res = res.setIn(['referrers', 'loaded'], false)
if (res.has('nft_tokens'))
res = res.delete('nft_tokens')
res = res.mergeDeep(payload);
@@ -235,6 +239,64 @@ export default createModule({
return new_state
},
},
+ {
+ action: 'FETCH_REFERRALS',
+ reducer: state => state,
+ },
+ {
+ action: 'RECEIVE_REFERRALS',
+ reducer: (state, { payload: { referrals, start_name, next_start_name } }) => {
+ let new_state = state
+ if (!start_name) {
+ new_state = new_state.set('referrals', fromJS({
+ data: referrals,
+ next_start_name,
+ loaded: true
+ }))
+ } else {
+ new_state = new_state.update('referrals', refs => {
+ refs = refs.update('data', data => {
+ for (const referral of referrals) {
+ data = data.push(fromJS(referral))
+ }
+ return data
+ })
+ refs = refs.set('next_start_name', next_start_name)
+ return refs
+ })
+ }
+ return new_state
+ },
+ },
+ {
+ action: 'FETCH_REFERRERS',
+ reducer: state => state,
+ },
+ {
+ action: 'RECEIVE_REFERRERS',
+ reducer: (state, { payload: { referrers, start_name, next_start_name } }) => {
+ let new_state = state
+ if (!start_name) {
+ new_state = new_state.set('referrers', fromJS({
+ data: referrers,
+ next_start_name,
+ loaded: true
+ }))
+ } else {
+ new_state = new_state.update('referrers', refs => {
+ refs = refs.update('data', data => {
+ for (const referrer of referrers) {
+ data = data.push(fromJS(referrer))
+ }
+ return data
+ })
+ refs = refs.set('next_start_name', next_start_name)
+ return refs
+ })
+ }
+ return new_state
+ },
+ },
{
action: 'LINK_REPLY',
reducer: (state, { payload: op }) => {
diff --git a/package.json b/package.json
index 629c949f..be5680ff 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"foundation-sites": "^6.4.3",
"fs-extra": "^10.0.1",
"git-rev-sync": "^3.0.2",
- "golos-lib-js": "^0.9.56",
+ "golos-lib-js": "^0.9.61",
"history": "^2.0.0-rc2",
"immutable": "^3.8.2",
"intl": "^1.2.5",
@@ -89,6 +89,7 @@
"react": "^18.2.0",
"react-addons-pure-render-mixin": "^15.6.3",
"react-cookie": "1.0.4",
+ "react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-dom-confetti": "^0.1.3",
"react-dropzone": "^4.2.12",
diff --git a/yarn.lock b/yarn.lock
index 23a8776e..36ac0f8b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2528,6 +2528,15 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
+call-bind@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513"
+ integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==
+ dependencies:
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.1"
+ set-function-length "^1.1.1"
+
caller-callsite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
@@ -3159,6 +3168,13 @@ copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
+copy-to-clipboard@^3.3.1:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
+ integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
+ dependencies:
+ toggle-selection "^1.0.6"
+
core-js-compat@^3.18.0, core-js-compat@^3.19.0:
version "3.19.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.19.1.tgz#fe598f1a9bf37310d77c3813968e9f7c7bb99476"
@@ -3177,9 +3193,9 @@ core-js@^2.4.0, core-js@^2.5.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-js@^3.17.3:
- version "3.32.2"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7"
- integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==
+ version "3.33.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.3.tgz#3c644a323f0f533a0d360e9191e37f7fc059088d"
+ integrity sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==
core-js@^3.19.0, core-js@^3.19.1:
version "3.19.1"
@@ -3753,10 +3769,10 @@ defaults@^1.0.3:
dependencies:
clone "^1.0.2"
-define-data-property@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451"
- integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==
+define-data-property@^1.0.1, define-data-property@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
+ integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
dependencies:
get-intrinsic "^1.2.1"
gopd "^1.0.1"
@@ -4993,6 +5009,11 @@ function-bind@^1.1.0, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -5054,15 +5075,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
-get-intrinsic@^1.1.3, get-intrinsic@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
- integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
+ integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
dependencies:
- function-bind "^1.1.1"
- has "^1.0.3"
+ function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
+ hasown "^2.0.0"
get-port@^3.2.0:
version "3.2.0"
@@ -5182,10 +5203,10 @@ globule@^1.0.0:
lodash "^4.17.21"
minimatch "~3.0.2"
-golos-lib-js@^0.9.56:
- version "0.9.56"
- resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.56.tgz#3dfe8c0658fba2f50976ef49103ddc3f34109c19"
- integrity sha512-h9ay0q2AuHiYL8aFXsCGoEFe6ojHt67FHMv8W6oWbqayl44JlRuuEysfE1MZQiiLwzBDFOO1SNMAtv5sE0bRcg==
+golos-lib-js@^0.9.61:
+ version "0.9.61"
+ resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.61.tgz#d54d5f5dc66eaa42b78047588903930eb8b029a8"
+ integrity sha512-OEZC/zov/Ur76UkI/AdieHWypSaqwWtcGWSccYzx/x/DaxMv+ExpdnmXrae7+OPmTur2Kj6f8/JhfZ1tgdgllg==
dependencies:
abort-controller "^3.0.0"
assert "^2.0.0"
@@ -5306,11 +5327,11 @@ has-flag@^4.0.0:
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-property-descriptors@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
- integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
+ integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
dependencies:
- get-intrinsic "^1.1.1"
+ get-intrinsic "^1.2.2"
has-proto@^1.0.1:
version "1.0.1"
@@ -5391,6 +5412,13 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
+hasown@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
+ integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
+ dependencies:
+ function-bind "^1.1.2"
+
he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
@@ -9019,7 +9047,7 @@ prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.1:
loose-envify "^1.3.1"
object-assign "^4.1.1"
-prop-types@^15.5.4, prop-types@^15.5.8:
+prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -9225,6 +9253,14 @@ react-cookie@1.0.4:
cookie "^0.3.1"
object-assign "^4.1.0"
+react-copy-to-clipboard@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c"
+ integrity sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==
+ dependencies:
+ copy-to-clipboard "^3.3.1"
+ prop-types "^15.8.1"
+
react-dom-confetti@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/react-dom-confetti/-/react-dom-confetti-0.1.4.tgz#64025805f58eecd58ee8184e3d0ad3fc25ff3aee"
@@ -10154,6 +10190,16 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+set-function-length@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
+ integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
+ dependencies:
+ define-data-property "^1.1.1"
+ get-intrinsic "^1.2.1"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.0"
+
set-value@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
@@ -11022,6 +11068,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
+toggle-selection@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+ integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
+
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@@ -11851,12 +11902,12 @@ which-boxed-primitive@^1.0.2:
is-symbol "^1.0.3"
which-typed-array@^1.1.11, which-typed-array@^1.1.2:
- version "1.1.11"
- resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a"
- integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36"
+ integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==
dependencies:
available-typed-arrays "^1.0.5"
- call-bind "^1.0.2"
+ call-bind "^1.0.4"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"