Skip to content

Commit

Permalink
Referrers, referrals
Browse files Browse the repository at this point in the history
  • Loading branch information
1aerostorm committed Nov 27, 2023
1 parent bb62cf3 commit e147a58
Show file tree
Hide file tree
Showing 16 changed files with 680 additions and 40 deletions.
7 changes: 5 additions & 2 deletions app/ResolveRoute.js
Original file line number Diff line number Diff line change
@@ -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)\/??(?:&?[^=&]*=[^=&]*)*$/,

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with '/@-/feed=' and containing many repetitions of '%='.
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-]+)\/?($|\?)/,
Expand Down Expand Up @@ -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'};
Expand Down
2 changes: 2 additions & 0 deletions app/RootRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
1 change: 1 addition & 0 deletions app/components/all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
30 changes: 21 additions & 9 deletions app/components/elements/TimeAgoWrapper.js
Original file line number Diff line number Diff line change
@@ -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 <Tooltip t={dt.toLocaleString()} className={className}>
<FormattedRelativeTime {...this.props} value={value} unit={unit} />
</Tooltip>
<FormattedRelativeTime {...this.props} value={value} unit={unit} />
</Tooltip>
}
}
8 changes: 8 additions & 0 deletions app/components/modules/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class Header extends React.Component {
page_title = tt('header_jsx.change_account_password');
} else if (route.page === 'MinusedAccounts') {
page_title = tt('minused_accounts_jsx.title');
} else if (route.page === 'Referrers') {
page_title = tt('referrers_jsx.title');
} else if (route.page === 'UserProfile') {
user_name = route.params[0].slice(1);
const acct_meta = this.props.account_meta.getIn([user_name]);
Expand All @@ -125,6 +127,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;
}
Expand Down
192 changes: 192 additions & 0 deletions app/components/modules/Referrals.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
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 = () => {
this.props.fetchReferrals(this.props.account, '', this.sort)
}

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 <div style={{ paddingBottom: '1rem' }}>
<h3>{tt('referrals_jsx.title')}</h3>
<LoadingIndicator type='circle' />
</div>
}

let refs = referrals.get('data').toJS()

let count = 0

let items = refs.map(ref => {
++count

const { accounts } = this.props
let acc = accounts.get(ref.account)
if (acc) acc = acc.toJS()

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 <tr key={count} className={count % 2 == 0 ? '' : 'zebra'}>
<td className={!inactive ? undefined : 'inactive-link'} title={!inactive ? (acc && (tt('referrals_jsx.end_date') + wrapDate(acc.referral_end_date, this.context))) : tt('referrals_jsx.inactive')}>
<LinkEx to={'/@' + ref.account}><b>
{ref.account}
</b></LinkEx>
</td>
{acc && <td className={tdClass} title={tt('referrals_jsx.vs')}>
{Asset(vestsToSteem(acc.vesting_shares, props) + ' GOLOS').floatString}
</td>}
{acc && <td className={tdClass} title={tt('referrals_jsx.posts')}>
{tt('user_profile.post_count', {count: acc.post_count || 0})}
</td>}
<td className={tdClass}>
<span title={tt('referrals_jsx.rewards')}>
{' +'}
{golosRewards.floatString}
</span>
</td>
<td className={tdClass} title={tt('referrals_jsx.joined')}>
<DateJoinWrapper date={ref.joined} />
</td>
<td className={tdClass} title={tt('referrals_jsx.last')}>
{lastSeen && <TimeAgoWrapper date={`${lastSeen}`} />}
</td>
</tr>
})

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 <div className='Referrals' style={{ paddingBottom: '1rem' }}>
<h3 style={{ display: 'inline-block' }}>{tt('referrals_jsx.title')}</h3>
<Link to={`/referrers`} className="button float-right">
{tt('referrers_jsx.button')}
</Link>
<div>{tt('referrals_jsx.desc')}</div>
{refUrl && <div style={{ display: 'inline-block', fontSize: '85%', marginTop: '1rem', marginBottom: '0.25rem' }}>
<Icon name="hf/hf5" size="2x" />
{' '}{tt('g.referral_link')}{' - '}
<span style={{border: '1px solid lightgray', padding: '5px', borderRadius: '3px'}}>
<a target="_blank" href={refUrl}>{refUrl}</a>

Check warning

Code scanning / CodeQL

Potentially unsafe external link Medium

External links without noopener/noreferrer are a potential security risk.
</span>
<CopyToClipboard text={refUrl}>
<span style={{cursor: 'pointer'}}>
<Icon name="copy" size="2x" />
</span>
</CopyToClipboard>
<span className='float-right'>&nbsp;&nbsp;</span>
</div>}
<div className='float-right' style={{ marginTop: '0.75rem '}} >
<DropdownMenu el='div' items={sortItems} selected={currentSort}>
<span title={tt('referrals_jsx.sort')} style={{ display: 'block', marginTop: '5px' }}>
{currentSort}
<Icon name='dropdown-arrow' size='0_95x' />
</span>
</DropdownMenu>
</div>
{items.length ? <table style={{marginTop: '1rem'}}>
<thead>
<th>{tt('referrals_jsx.name')}</th>
<th>{tt('referrals_jsx.vs')}</th>
<th>{tt('referrals_jsx.posts')}</th>
<th>{tt('referrals_jsx.rewards')}</th>
<th>{tt('referrals_jsx.joined')}</th>
<th>{tt('referrals_jsx.last')}</th>
</thead>
<tbody>
{items}
</tbody></table> : null}
{next_start_name ? <div className='load-more' key='load_more'>
<center><button className='button hollow small' onClick={
e => this.props.fetchReferrals(this.props.account, next_start_name, this.sort)
}>{tt('g.load_more')}</button></center>
</div> : null}
</div>
}
}


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)
10 changes: 10 additions & 0 deletions app/components/modules/Referrals.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.Referrals {
.inactive-link {
a {
color: gray;
}
}
.inactive {
opacity: 0.5;
}
}
4 changes: 2 additions & 2 deletions app/components/modules/TopRightMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops,
if (loggedIn) { // change back to if(username) after bug fix: Clicking on Login does not cause drop-down to close #TEMP!
let user_menu = [
{link: feedLink, icon: 'new/home', value: tt('g.feed'), addon: <NotifiCounter fields="feed" />},
{link: accountLink, icon: 'new/blogging', value: tt('g.blog'), addon: <NotifiCounter fields="new_sponsor,sponsor_inactive" />},
{link: accountLink, icon: 'new/blogging', value: tt('g.blog'), addon: <NotifiCounter fields="new_sponsor,sponsor_inactive,referral" />},
{link: repliesLink, icon: 'new/answer', value: tt('g.replies'), addon: <NotifiCounter fields="comment_reply" />},
{link: discussionsLink, icon: 'new/bell', value: tt('g.discussions'), addon: <NotifiCounter fields="subscriptions" />},
{link: mentionsLink, icon: 'new/mention', value: tt('g.mentions'), addon: <NotifiCounter fields="mention" />},
Expand Down Expand Up @@ -199,7 +199,7 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops,
</div>
</div>
</a>
<div className="TopRightMenu__notificounter"><NotifiCounter fields="feed,subscriptions,send,receive,delegate_vs,donate,donate_msgs,message,fill_order,comment_reply,mention,new_sponsor,sponsor_inactive,nft_receive" /></div>
<div className="TopRightMenu__notificounter"><NotifiCounter fields="feed,subscriptions,send,receive,delegate_vs,donate,donate_msgs,message,fill_order,comment_reply,mention,new_sponsor,sponsor_inactive,nft_receive,referral" /></div>
</li>}
</LinkWithDropdown>
{navAdditional}
Expand Down
Loading

0 comments on commit e147a58

Please sign in to comment.