invites basics
This commit is contained in:
parent
1cbc96533b
commit
31fb723d87
18 changed files with 404 additions and 7 deletions
|
@ -2,6 +2,7 @@ import * as types from "constants/action_types";
|
|||
import * as modals from "constants/modal_types";
|
||||
import lbryio from "lbryio";
|
||||
import { doOpenModal } from "actions/app";
|
||||
import { doOpenModal, doShowSnackBar } from "actions/app";
|
||||
import { doRewardList, doClaimRewardType } from "actions/rewards";
|
||||
import { selectEmailToVerify, selectUser } from "selectors/user";
|
||||
import rewards from "rewards";
|
||||
|
@ -172,3 +173,61 @@ export function doFetchAccessToken() {
|
|||
lbryio.getAuthToken().then(success);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchInviteStatus() {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.USER_INVITE_STATUS_FETCH_STARTED,
|
||||
});
|
||||
|
||||
lbryio
|
||||
.call("user", "invite_status")
|
||||
.then(status => {
|
||||
console.log(status);
|
||||
dispatch({
|
||||
type: types.USER_INVITE_STATUS_FETCH_SUCCESS,
|
||||
data: {
|
||||
invitesRemaining: status.invites_remaining,
|
||||
invitees: status.invitees,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: types.USER_INVITE_STATUS_FETCH_FAILURE,
|
||||
data: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doUserInviteNew(email) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.USER_INVITE_NEW_STARTED,
|
||||
});
|
||||
|
||||
lbryio
|
||||
.call("user", "invite", { email: email }, "post")
|
||||
.then(invite => {
|
||||
dispatch({
|
||||
type: types.USER_INVITE_NEW_SUCCESS,
|
||||
data: { email },
|
||||
});
|
||||
|
||||
dispatch(
|
||||
doShowSnackBar({
|
||||
message: __("Invite sent to %s", email),
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(doFetchInviteStatus());
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: types.USER_INVITE_NEW_FAILURE,
|
||||
data: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
16
ui/js/component/inviteList/index.js
Normal file
16
ui/js/component/inviteList/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
selectUserInvitees,
|
||||
selectUserInviteStatusIsPending,
|
||||
} from "selectors/user";
|
||||
import InviteList from "./view";
|
||||
|
||||
const select = state => ({
|
||||
invitees: selectUserInvitees(state),
|
||||
isPending: selectUserInviteStatusIsPending(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(select, perform)(InviteList);
|
58
ui/js/component/inviteList/view.jsx
Normal file
58
ui/js/component/inviteList/view.jsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React from "react";
|
||||
import { Icon } from "component/common";
|
||||
|
||||
class InviteList extends React.PureComponent {
|
||||
render() {
|
||||
const { invitees } = this.props;
|
||||
|
||||
if (!invitees) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<h3>{__("Invite History")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
{invitees.length === 0 &&
|
||||
<span className="empty">{__("You haven't invited anyone.")} </span>}
|
||||
{invitees.length > 0 &&
|
||||
<table className="table-standard table-stretch">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{__("Invitee Email")}
|
||||
</th>
|
||||
<th className="text-center">
|
||||
{__("Invite Status")}
|
||||
</th>
|
||||
<th className="text-center">
|
||||
{__("Reward")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{invitees.map((invitee, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{invitee.email}</td>
|
||||
<td className="text-center">
|
||||
{invitee.invite_accepted && <Icon icon="icon-check" />}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{invitee.invite_reward_claimed &&
|
||||
<Icon icon="icon-check" />}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteList;
|
21
ui/js/component/inviteNew/index.js
Normal file
21
ui/js/component/inviteNew/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import InviteNew from "./view";
|
||||
import {
|
||||
selectUserInvitesRemaining,
|
||||
selectUserInviteNewIsPending,
|
||||
selectUserInviteNewErrorMessage,
|
||||
} from "selectors/user";
|
||||
import { doUserInviteNew } from "actions/user";
|
||||
|
||||
const select = state => ({
|
||||
errorMessage: selectUserInviteNewErrorMessage(state),
|
||||
invitesRemaining: selectUserInvitesRemaining(state),
|
||||
isPending: selectUserInviteNewIsPending(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
inviteNew: email => dispatch(doUserInviteNew(email)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(InviteNew);
|
100
ui/js/component/inviteNew/view.jsx
Normal file
100
ui/js/component/inviteNew/view.jsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React from "react";
|
||||
import { BusyMessage } from "component/common";
|
||||
import Link from "component/link";
|
||||
import { FormRow } from "component/form.js";
|
||||
|
||||
class FormInviteNew extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
email: "",
|
||||
};
|
||||
}
|
||||
|
||||
handleEmailChanged(event) {
|
||||
this.setState({
|
||||
email: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.props.inviteNew(this.state.email);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { errorMessage, isPending } = this.props;
|
||||
|
||||
return (
|
||||
<form>
|
||||
<FormRow
|
||||
type="text"
|
||||
label="Email"
|
||||
placeholder="youremail@example.org"
|
||||
name="email"
|
||||
value={this.state.email}
|
||||
errorMessage={errorMessage}
|
||||
onChange={event => {
|
||||
this.handleEmailChanged(event);
|
||||
}}
|
||||
/>
|
||||
<div className="form-row-submit">
|
||||
<Link
|
||||
button="primary"
|
||||
label={__("Send Invite")}
|
||||
disabled={isPending}
|
||||
onClick={event => {
|
||||
this.handleSubmit(event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InviteNew extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
errorMessage,
|
||||
invitesRemaining,
|
||||
inviteNew,
|
||||
inviteStatusIsPending,
|
||||
isPending,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<h3>
|
||||
{__(
|
||||
"Invite a Friend (or Enemy) (or Someone You Are Somewhat Ambivalent About)"
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
{invitesRemaining > 0 &&
|
||||
<p>{__("You have %s invites remaining.", invitesRemaining)}</p>}
|
||||
{invitesRemaining <= 0 &&
|
||||
<p>
|
||||
<span className="empty">
|
||||
{__("You have no invites.", invitesRemaining)}
|
||||
</span>
|
||||
</p>}
|
||||
</div>
|
||||
{!inviteStatusIsPending &&
|
||||
invitesRemaining > 0 &&
|
||||
<div className="card__content">
|
||||
<FormInviteNew
|
||||
errorMessage={errorMessage}
|
||||
inviteNew={inviteNew}
|
||||
isPending={isPending}
|
||||
/>
|
||||
</div>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteNew;
|
|
@ -16,6 +16,7 @@ import FileListPublished from "page/fileListPublished";
|
|||
import ChannelPage from "page/channel";
|
||||
import SearchPage from "page/search";
|
||||
import AuthPage from "page/auth";
|
||||
import InvitePage from "page/invite";
|
||||
import BackupPage from "page/backup";
|
||||
|
||||
const route = (page, routesMap) => {
|
||||
|
@ -35,6 +36,7 @@ const Router = props => {
|
|||
discover: <DiscoverPage params={params} />,
|
||||
downloaded: <FileListDownloaded params={params} />,
|
||||
help: <HelpPage params={params} />,
|
||||
invite: <InvitePage params={params} />,
|
||||
publish: <PublishPage params={params} />,
|
||||
published: <FileListPublished params={params} />,
|
||||
receive: <ReceiveCreditsPage params={params} />,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doUserEmailNew } from "actions/user";
|
||||
import { doUserEmailNew, doUserInviteNew } from "actions/user";
|
||||
import {
|
||||
selectEmailNewIsPending,
|
||||
selectEmailNewErrorMessage,
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
selectReceiveAddress,
|
||||
selectGettingNewAddress,
|
||||
} from "selectors/wallet";
|
||||
import WalletPage from "./view";
|
||||
import WalletAddress from "./view";
|
||||
|
||||
const select = state => ({
|
||||
receiveAddress: selectReceiveAddress(state),
|
||||
|
@ -17,4 +17,4 @@ const perform = dispatch => ({
|
|||
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(WalletPage);
|
||||
export default connect(select, perform)(WalletAddress);
|
||||
|
|
|
@ -108,6 +108,15 @@ export const USER_IDENTITY_VERIFY_FAILURE = "USER_IDENTITY_VERIFY_FAILURE";
|
|||
export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
|
||||
export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
|
||||
export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
|
||||
export const USER_INVITE_STATUS_FETCH_STARTED =
|
||||
"USER_INVITE_STATUS_FETCH_STARTED";
|
||||
export const USER_INVITE_STATUS_FETCH_SUCCESS =
|
||||
"USER_INVITE_STATUS_FETCH_SUCCESS";
|
||||
export const USER_INVITE_STATUS_FETCH_FAILURE =
|
||||
"USER_INVITE_STATUS_FETCH_FAILURE";
|
||||
export const USER_INVITE_NEW_STARTED = "USER_INVITE_NEW_STARTED";
|
||||
export const USER_INVITE_NEW_SUCCESS = "USER_INVITE_NEW_SUCCESS";
|
||||
export const USER_INVITE_NEW_FAILURE = "USER_INVITE_NEW_FAILURE";
|
||||
export const FETCH_ACCESS_TOKEN_SUCCESS = "FETCH_ACCESS_TOKEN_SUCCESS";
|
||||
|
||||
// Rewards
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { Modal } from "modal/modal";
|
||||
import { CreditAmount } from "component/common";
|
||||
import { CreditAmount, CurrencySymbol } from "component/common";
|
||||
import Link from "component/link/index";
|
||||
|
||||
const ModalCreditIntro = props => {
|
||||
|
@ -14,7 +14,7 @@ const ModalCreditIntro = props => {
|
|||
<h3 className="modal__header">{__("Claim Your Credits")}</h3>
|
||||
<p>
|
||||
The LBRY network is controlled and powered by credits called{" "}
|
||||
<em>LBC</em>, a blockchain asset.
|
||||
<em><CurrencySymbol /></em>, a blockchain asset.
|
||||
</p>
|
||||
<p>
|
||||
{__("New patrons receive ")} {" "}
|
||||
|
|
19
ui/js/page/invite/index.js
Normal file
19
ui/js/page/invite/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import InvitePage from "./view";
|
||||
import { doFetchInviteStatus } from "actions/user";
|
||||
import {
|
||||
selectUserInviteStatusFailed,
|
||||
selectUserInviteStatusIsPending,
|
||||
} from "selectors/user";
|
||||
|
||||
const select = state => ({
|
||||
isFailed: selectUserInviteStatusFailed(state),
|
||||
isPending: selectUserInviteStatusIsPending(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
fetchInviteStatus: () => dispatch(doFetchInviteStatus()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(InvitePage);
|
32
ui/js/page/invite/view.jsx
Normal file
32
ui/js/page/invite/view.jsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import React from "react";
|
||||
import { BusyMessage } from "component/common";
|
||||
import SubHeader from "component/subHeader";
|
||||
import InviteNew from "component/inviteNew";
|
||||
import InviteList from "component/inviteList";
|
||||
|
||||
class InvitePage extends React.PureComponent {
|
||||
componentWillMount() {
|
||||
this.props.fetchInviteStatus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isPending, isFailed } = this.props;
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
{isPending &&
|
||||
<BusyMessage message={__("Checking your invite status")} />}
|
||||
{!isPending &&
|
||||
isFailed &&
|
||||
<span className="empty">
|
||||
{__("Failed to retrieve invite status.")}
|
||||
</span>}
|
||||
{!isPending && !isFailed && <InviteNew />}
|
||||
{!isPending && !isFailed && <InviteList />}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InvitePage;
|
|
@ -7,7 +7,6 @@ const ReceiveCreditsPage = props => {
|
|||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<WalletBalance />
|
||||
<WalletAddress />
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@ const SendCreditsPage = props => {
|
|||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<WalletBalance />
|
||||
<WalletSend />
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,11 @@ const defaultState = {
|
|||
emailNewIsPending: false,
|
||||
emailNewErrorMessage: "",
|
||||
emailToVerify: "",
|
||||
inviteNewErrorMessage: "",
|
||||
inviteNewIsPending: false,
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: undefined,
|
||||
invitees: undefined,
|
||||
user: undefined,
|
||||
};
|
||||
|
||||
|
@ -142,6 +147,49 @@ reducers[types.FETCH_ACCESS_TOKEN_SUCCESS] = function(state, action) {
|
|||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_STARTED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: true,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_SUCCESS] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: action.data.invitesRemaining,
|
||||
invitees: action.data.invitees,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_STARTED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: true,
|
||||
inviteNewErrorMessage: "",
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_SUCCESS] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: false,
|
||||
inviteNewErrorMessage: "",
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_FAILURE] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: false,
|
||||
inviteNewErrorMessage: action.data.error.message,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_FAILURE] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: null,
|
||||
invitees: null,
|
||||
});
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -138,12 +138,14 @@ export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
|
|||
case "wallet":
|
||||
case "send":
|
||||
case "receive":
|
||||
case "invite":
|
||||
case "rewards":
|
||||
case "backup":
|
||||
return {
|
||||
wallet: __("Overview"),
|
||||
send: __("Send"),
|
||||
receive: __("Receive"),
|
||||
invite: __("Invites"),
|
||||
rewards: __("Rewards"),
|
||||
};
|
||||
case "downloaded":
|
||||
|
|
|
@ -68,3 +68,33 @@ export const selectAccessToken = createSelector(
|
|||
_selectState,
|
||||
state => state.accessToken
|
||||
);
|
||||
|
||||
export const selectUserInviteStatusIsPending = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteStatusIsPending
|
||||
);
|
||||
|
||||
export const selectUserInvitesRemaining = createSelector(
|
||||
_selectState,
|
||||
state => state.invitesRemaining
|
||||
);
|
||||
|
||||
export const selectUserInvitees = createSelector(
|
||||
_selectState,
|
||||
state => state.invitees
|
||||
);
|
||||
|
||||
export const selectUserInviteStatusFailed = createSelector(
|
||||
selectUserInvitesRemaining,
|
||||
inviteStatus => selectUserInvitesRemaining === null
|
||||
);
|
||||
|
||||
export const selectUserInviteNewIsPending = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteNewIsPending
|
||||
);
|
||||
|
||||
export const selectUserInviteNewErrorMessage = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteNewErrorMessage
|
||||
);
|
||||
|
|
|
@ -24,6 +24,9 @@ table.table-standard {
|
|||
img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
tr.thead:not(:first-child) th {
|
||||
border-top: 1px solid #e2e2e2;
|
||||
|
|
Loading…
Reference in a new issue