fe: Placeholder loading state & use it for profile tabs + RFPs list (#255)
This commit is contained in:
parent
c6318e27f7
commit
0b2676563a
|
@ -1,16 +1,22 @@
|
|||
import React from 'react';
|
||||
import './style.less';
|
||||
import Loader from 'components/Loader';
|
||||
|
||||
interface Props {
|
||||
title?: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const Placeholder: React.SFC<Props> = ({ style = {}, title, subtitle }) => (
|
||||
<div className="Placeholder" style={style}>
|
||||
{title && <h3 className="Placeholder-title">{title}</h3>}
|
||||
{subtitle && <div className="Placeholder-subtitle">{subtitle}</div>}
|
||||
const Placeholder: React.SFC<Props> = ({ style = {}, title, subtitle, loading }) => (
|
||||
<div className={`Placeholder ${!!loading && 'is-loading'}`} style={style}>
|
||||
{(loading && <Loader inline={true} size="small" />) || (
|
||||
<>
|
||||
{title && <h3 className="Placeholder-title">{title}</h3>}
|
||||
{subtitle && <div className="Placeholder-subtitle">{subtitle}</div>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '~styles/variables.less';
|
||||
|
||||
.Placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -8,6 +10,10 @@
|
|||
padding: 3rem;
|
||||
border-radius: 8px;
|
||||
|
||||
&.is-loading {
|
||||
border-color: fadeout(@primary-color, 60%);
|
||||
}
|
||||
|
||||
&-title {
|
||||
margin-bottom: 0;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
|
|
|
@ -95,6 +95,8 @@ class Profile extends React.Component<Props, State> {
|
|||
invites,
|
||||
arbitrated,
|
||||
} = user;
|
||||
|
||||
const isLoading = user.isFetching;
|
||||
const nonePending = pendingProposals.length === 0;
|
||||
const noneCreated = proposals.length === 0;
|
||||
const noneFunded = contributions.length === 0;
|
||||
|
@ -132,6 +134,7 @@ class Profile extends React.Component<Props, State> {
|
|||
<div>
|
||||
{nonePending && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No pending proposals"
|
||||
subtitle="You do not have any proposals awaiting approval."
|
||||
/>
|
||||
|
@ -144,6 +147,7 @@ class Profile extends React.Component<Props, State> {
|
|||
<div>
|
||||
{noneCreated && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No created proposals"
|
||||
subtitle="There have not been any created proposals."
|
||||
/>
|
||||
|
@ -157,6 +161,7 @@ class Profile extends React.Component<Props, State> {
|
|||
<div>
|
||||
{noneFunded && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No proposals funded"
|
||||
subtitle="There have not been any proposals funded."
|
||||
/>
|
||||
|
@ -175,6 +180,7 @@ class Profile extends React.Component<Props, State> {
|
|||
<div>
|
||||
{noneCommented && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No comments"
|
||||
subtitle="There have not been any comments made"
|
||||
/>
|
||||
|
@ -193,6 +199,7 @@ class Profile extends React.Component<Props, State> {
|
|||
<div>
|
||||
{noneInvites && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No invitations"
|
||||
subtitle="You’ll be notified when you’ve been invited to join a proposal"
|
||||
/>
|
||||
|
@ -210,6 +217,7 @@ class Profile extends React.Component<Props, State> {
|
|||
>
|
||||
{noneArbitrated && (
|
||||
<Placeholder
|
||||
loading={isLoading}
|
||||
title="No arbitrations"
|
||||
subtitle="You are not an arbiter of any proposals"
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ import './index.less';
|
|||
interface StateProps {
|
||||
rfps: AppState['rfps']['rfps'];
|
||||
isFetchingRfps: AppState['rfps']['isFetchingRfps'];
|
||||
hasFetchedRfps: AppState['rfps']['hasFetchedRfps'];
|
||||
fetchRfpsError: AppState['rfps']['fetchRfpsError'];
|
||||
}
|
||||
|
||||
|
@ -29,7 +30,7 @@ class RFPs extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { rfps, isFetchingRfps, fetchRfpsError } = this.props;
|
||||
const { rfps, isFetchingRfps, hasFetchedRfps, fetchRfpsError } = this.props;
|
||||
|
||||
let rfpsEl;
|
||||
if (fetchRfpsError) {
|
||||
|
@ -41,7 +42,7 @@ class RFPs extends React.Component<Props> {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (!isFetchingRfps) {
|
||||
} else if (!hasFetchedRfps && isFetchingRfps) {
|
||||
rfpsEl = (
|
||||
<div className="RFPs-loading">
|
||||
<Loader size="large" />
|
||||
|
@ -95,6 +96,7 @@ class RFPs extends React.Component<Props> {
|
|||
<Placeholder
|
||||
title="No requests are currently active"
|
||||
subtitle="Check back later for more opportunities"
|
||||
loading={this.props.isFetchingRfps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -106,6 +108,7 @@ export default connect<StateProps, DispatchProps, {}, AppState>(
|
|||
state => ({
|
||||
rfps: state.rfps.rfps,
|
||||
isFetchingRfps: state.rfps.isFetchingRfps,
|
||||
hasFetchedRfps: state.rfps.hasFetchedRfps,
|
||||
fetchRfpsError: state.rfps.fetchRfpsError,
|
||||
}),
|
||||
{ fetchRfps },
|
||||
|
|
|
@ -5,18 +5,18 @@ export interface RFPState {
|
|||
rfps: RFP[];
|
||||
fetchRfpsError: null | string;
|
||||
isFetchingRfps: boolean;
|
||||
hasFetchedRfps: boolean;
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: RFPState = {
|
||||
rfps: [],
|
||||
fetchRfpsError: null,
|
||||
isFetchingRfps: false,
|
||||
hasFetchedRfps: false,
|
||||
};
|
||||
|
||||
function addRfp(state: RFPState, payload: RFP) {
|
||||
const existingProposal = state.rfps.find(
|
||||
(rfp: RFP) => rfp.id === payload.id,
|
||||
);
|
||||
const existingProposal = state.rfps.find((rfp: RFP) => rfp.id === payload.id);
|
||||
|
||||
const rfps = [...state.rfps];
|
||||
if (!existingProposal) {
|
||||
|
@ -56,7 +56,8 @@ export default (state: RFPState = INITIAL_STATE, action: any): RFPState => {
|
|||
...state,
|
||||
rfps: payload,
|
||||
fetchRfpsError: null,
|
||||
isFetchingRfps: true,
|
||||
isFetchingRfps: false,
|
||||
hasFetchedRfps: true,
|
||||
};
|
||||
|
||||
case types.FETCH_RFP_FULFILLED:
|
||||
|
|
Loading…
Reference in New Issue