Check in user refactor. Incomplete, but computer is crashing routinely.
This commit is contained in:
parent
89c1c3345d
commit
d7e4c1c533
|
@ -3,6 +3,7 @@ from grant.comment.models import Comment
|
|||
from grant.email.models import EmailVerification
|
||||
from grant.extensions import ma, db
|
||||
from grant.utils.misc import make_url
|
||||
from grant.utils.social import get_social_info_from_url
|
||||
from grant.email.send import send_email
|
||||
|
||||
|
||||
|
@ -122,7 +123,21 @@ class SocialMediaSchema(ma.Schema):
|
|||
class Meta:
|
||||
model = SocialMedia
|
||||
# Fields to expose
|
||||
fields = ("social_media_link",)
|
||||
fields = (
|
||||
"service",
|
||||
"username",
|
||||
)
|
||||
|
||||
service = ma.Method("get_service")
|
||||
username = ma.Method("get_username")
|
||||
|
||||
def get_service(self, obj):
|
||||
info = get_social_info_from_url(obj.social_media_link)
|
||||
return info['service']
|
||||
|
||||
def get_username(self, obj):
|
||||
info = get_social_info_from_url(obj.social_media_link)
|
||||
return info['username']
|
||||
|
||||
|
||||
social_media_schema = SocialMediaSchema()
|
||||
|
|
|
@ -39,9 +39,13 @@ def get_me():
|
|||
@blueprint.route("/<user_identity>", methods=["GET"])
|
||||
@endpoint.api()
|
||||
def get_user(user_identity):
|
||||
print('get by ident')
|
||||
user = User.get_by_identifier(email_address=user_identity, account_address=user_identity)
|
||||
print(user)
|
||||
if user:
|
||||
print('dumping')
|
||||
result = user_schema.dump(user)
|
||||
print(result)
|
||||
return result
|
||||
else:
|
||||
message = "User with account_address or user_identity matching {} not found".format(user_identity)
|
||||
|
@ -126,7 +130,7 @@ def auth_user(account_address, signed_message, raw_typed_data):
|
|||
parameter('displayName', type=str, required=True),
|
||||
parameter('title', type=str, required=True),
|
||||
parameter('socialMedias', type=list, required=True),
|
||||
parameter('avatar', type=dict, required=True)
|
||||
parameter('avatar', type=str, required=True)
|
||||
)
|
||||
def update_user(user_identity, display_name, title, social_medias, avatar):
|
||||
user = g.current_user
|
||||
|
@ -137,22 +141,20 @@ def update_user(user_identity, display_name, title, social_medias, avatar):
|
|||
if title is not None:
|
||||
user.title = title
|
||||
|
||||
db_socials = SocialMedia.query.filter_by(user_id=user.id).all()
|
||||
for db_social in db_socials:
|
||||
db.session.delete(db_social)
|
||||
if social_medias is not None:
|
||||
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||
for social_media in social_medias:
|
||||
sm = SocialMedia(social_media_link=social_media.get("link"), user_id=user.id)
|
||||
sm = SocialMedia(social_media_link=social_media, user_id=user.id)
|
||||
db.session.add(sm)
|
||||
else:
|
||||
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
if avatar is not None:
|
||||
Avatar.query.filter_by(user_id=user.id).delete()
|
||||
avatar_link = avatar.get('link')
|
||||
if avatar_link:
|
||||
avatar_obj = Avatar(image_url=avatar_link, user_id=user.id)
|
||||
db.session.add(avatar_obj)
|
||||
else:
|
||||
Avatar.query.filter_by(user_id=user.id).delete()
|
||||
db_avatar = Avatar.query.filter_by(user_id=user.id).first()
|
||||
if db_avatar:
|
||||
db.session.delete(db_avatar)
|
||||
if avatar:
|
||||
new_avatar = Avatar(image_url=avatar, user_id=user.id)
|
||||
db.session.add(new_avatar)
|
||||
|
||||
db.session.commit()
|
||||
result = user_schema.dump(user)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import re
|
||||
|
||||
username_regex = '([a-zA-Z0-9-_]*)'
|
||||
|
||||
social_patterns = {
|
||||
'GITHUB': 'https://github.com/{}'.format(username_regex),
|
||||
'TWITTER': 'https://twitter.com/{}'.format(username_regex),
|
||||
'LINKEDIN': 'https://linkedin.com/in/{}'.format(username_regex),
|
||||
'KEYBASE': 'https://keybase.io/{}'.format(username_regex),
|
||||
}
|
||||
|
||||
def get_social_info_from_url(url: str):
|
||||
for service, pattern in social_patterns.items():
|
||||
match = re.match(pattern, url)
|
||||
if match:
|
||||
return {
|
||||
'service': service,
|
||||
'username': match.group(1),
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -2,21 +2,16 @@ import axios from './axios';
|
|||
import {
|
||||
Proposal,
|
||||
ProposalDraft,
|
||||
TeamMember,
|
||||
User,
|
||||
Update,
|
||||
TeamInvite,
|
||||
TeamInviteWithProposal,
|
||||
} from 'types';
|
||||
import {
|
||||
formatTeamMemberForPost,
|
||||
formatTeamMemberFromGet,
|
||||
generateProposalUrl,
|
||||
} from 'utils/api';
|
||||
import { formatUserForPost, generateProposalUrl } from 'utils/api';
|
||||
|
||||
export function getProposals(): Promise<{ data: Proposal[] }> {
|
||||
return axios.get('/api/v1/proposals/').then(res => {
|
||||
res.data = res.data.map((proposal: any) => {
|
||||
proposal.team = proposal.team.map(formatTeamMemberFromGet);
|
||||
proposal.proposalUrlId = generateProposalUrl(proposal.proposalId, proposal.title);
|
||||
return proposal;
|
||||
});
|
||||
|
@ -26,7 +21,6 @@ export function getProposals(): Promise<{ data: Proposal[] }> {
|
|||
|
||||
export function getProposal(proposalId: number | string): Promise<{ data: Proposal }> {
|
||||
return axios.get(`/api/v1/proposals/${proposalId}`).then(res => {
|
||||
res.data.team = res.data.team.map(formatTeamMemberFromGet);
|
||||
res.data.proposalUrlId = generateProposalUrl(res.data.proposalId, res.data.title);
|
||||
return res;
|
||||
});
|
||||
|
@ -44,15 +38,12 @@ export function postProposal(payload: ProposalDraft) {
|
|||
return axios.post(`/api/v1/proposals/`, {
|
||||
...payload,
|
||||
// Team has a different shape for POST
|
||||
team: payload.team.map(formatTeamMemberForPost),
|
||||
team: payload.team.map(formatUserForPost),
|
||||
});
|
||||
}
|
||||
|
||||
export function getUser(address: string): Promise<{ data: TeamMember }> {
|
||||
return axios.get(`/api/v1/users/${address}`).then(res => {
|
||||
res.data = formatTeamMemberFromGet(res.data);
|
||||
return res;
|
||||
});
|
||||
export function getUser(address: string): Promise<{ data: User }> {
|
||||
return axios.get(`/api/v1/users/${address}`);
|
||||
}
|
||||
|
||||
export function createUser(payload: {
|
||||
|
@ -62,31 +53,20 @@ export function createUser(payload: {
|
|||
title: string;
|
||||
signedMessage: string;
|
||||
rawTypedData: string;
|
||||
}): Promise<{ data: TeamMember }> {
|
||||
return axios.post('/api/v1/users', payload).then(res => {
|
||||
res.data = formatTeamMemberFromGet(res.data);
|
||||
return res;
|
||||
});
|
||||
}): Promise<{ data: User }> {
|
||||
return axios.post('/api/v1/users', payload);
|
||||
}
|
||||
|
||||
export function authUser(payload: {
|
||||
accountAddress: string;
|
||||
signedMessage: string;
|
||||
rawTypedData: string;
|
||||
}): Promise<{ data: TeamMember }> {
|
||||
return axios.post('/api/v1/users/auth', payload).then(res => {
|
||||
res.data = formatTeamMemberFromGet(res.data);
|
||||
return res;
|
||||
});
|
||||
}): Promise<{ data: User }> {
|
||||
return axios.post('/api/v1/users/auth', payload);
|
||||
}
|
||||
|
||||
export function updateUser(user: TeamMember): Promise<{ data: TeamMember }> {
|
||||
return axios
|
||||
.put(`/api/v1/users/${user.ethAddress}`, formatTeamMemberForPost(user))
|
||||
.then(res => {
|
||||
res.data = formatTeamMemberFromGet(res.data);
|
||||
return res;
|
||||
});
|
||||
export function updateUser(user: User): Promise<{ data: User }> {
|
||||
return axios.put(`/api/v1/users/${user.accountAddress}`, formatUserForPost(user));
|
||||
}
|
||||
|
||||
export function verifyEmail(code: string): Promise<any> {
|
||||
|
@ -105,13 +85,7 @@ export function postProposalUpdate(
|
|||
}
|
||||
|
||||
export function getProposalDrafts(): Promise<{ data: ProposalDraft[] }> {
|
||||
return axios.get('/api/v1/proposals/drafts').then(res => {
|
||||
res.data = res.data.map((draft: any) => ({
|
||||
...draft,
|
||||
team: draft.team.map(formatTeamMemberFromGet),
|
||||
}));
|
||||
return res;
|
||||
});
|
||||
return axios.get('/api/v1/proposals/drafts');
|
||||
}
|
||||
|
||||
export function postProposalDraft(): Promise<{ data: ProposalDraft }> {
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Button, Alert } from 'antd';
|
||||
import { authActions } from 'modules/auth';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { AUTH_PROVIDER } from 'utils/auth';
|
||||
import Identicon from 'components/Identicon';
|
||||
|
@ -20,7 +20,7 @@ interface DispatchProps {
|
|||
|
||||
interface OwnProps {
|
||||
// TODO: Use common use User type instead
|
||||
user: TeamMember;
|
||||
user: User;
|
||||
provider: AUTH_PROVIDER;
|
||||
reset(): void;
|
||||
}
|
||||
|
@ -34,11 +34,14 @@ class SignIn extends React.Component<Props> {
|
|||
<div className="SignIn">
|
||||
<div className="SignIn-container">
|
||||
<div className="SignIn-identity">
|
||||
<Identicon address={user.ethAddress} className="SignIn-identity-identicon" />
|
||||
<Identicon
|
||||
address={user.accountAddress}
|
||||
className="SignIn-identity-identicon"
|
||||
/>
|
||||
<div className="SignIn-identity-info">
|
||||
<div className="SignIn-identity-info-name">{user.name}</div>
|
||||
<div className="SignIn-identity-info-name">{user.displayName}</div>
|
||||
<code className="SignIn-identity-info-address">
|
||||
<ShortAddress address={user.ethAddress} />
|
||||
<ShortAddress address={user.accountAddress} />
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,7 +72,7 @@ class SignIn extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private authUser = () => {
|
||||
this.props.authUser(this.props.user.ethAddress);
|
||||
this.props.authUser(this.props.user.accountAddress);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ const ReviewTeam: React.SFC<{
|
|||
<div className="ReviewTeam-member" key={idx}>
|
||||
<UserAvatar className="ReviewTeam-member-avatar" user={u} />
|
||||
<div className="ReviewTeam-member-info">
|
||||
<div className="ReviewTeam-member-info-name">{u.name}</div>
|
||||
<div className="ReviewTeam-member-info-name">{u.displayName}</div>
|
||||
<div className="ReviewTeam-member-info-title">{u.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Icon, Form, Input, Button, Popconfirm, message } from 'antd';
|
||||
import { TeamMember, TeamInvite, ProposalDraft } from 'types';
|
||||
import { User, TeamInvite, ProposalDraft } from 'types';
|
||||
import TeamMemberComponent from './TeamMember';
|
||||
import { postProposalInvite, deleteProposalInvite } from 'api/api';
|
||||
import { isValidEthAddress, isValidEmail } from 'utils/validators';
|
||||
|
@ -9,7 +9,7 @@ import { AppState } from 'store/reducers';
|
|||
import './Team.less';
|
||||
|
||||
interface State {
|
||||
team: TeamMember[];
|
||||
team: User[];
|
||||
invites: TeamInvite[];
|
||||
address: string;
|
||||
}
|
||||
|
@ -28,16 +28,7 @@ type Props = OwnProps & StateProps;
|
|||
|
||||
const MAX_TEAM_SIZE = 6;
|
||||
const DEFAULT_STATE: State = {
|
||||
team: [
|
||||
{
|
||||
name: '',
|
||||
title: '',
|
||||
avatarUrl: '',
|
||||
ethAddress: '',
|
||||
emailAddress: '',
|
||||
socialAccounts: {},
|
||||
},
|
||||
],
|
||||
team: [],
|
||||
invites: [],
|
||||
address: '',
|
||||
};
|
||||
|
@ -50,16 +41,8 @@ class CreateFlowTeam extends React.Component<Props, State> {
|
|||
...(props.initialState || {}),
|
||||
};
|
||||
|
||||
// Don't allow for empty team array
|
||||
if (!this.state.team.length) {
|
||||
this.state = {
|
||||
...this.state,
|
||||
team: [...DEFAULT_STATE.team],
|
||||
};
|
||||
}
|
||||
|
||||
// Auth'd user is always first member of a team
|
||||
if (props.authUser) {
|
||||
if (props.authUser && !this.state.team.length) {
|
||||
this.state.team[0] = {
|
||||
...props.authUser,
|
||||
};
|
||||
|
@ -77,8 +60,8 @@ class CreateFlowTeam extends React.Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="TeamForm">
|
||||
{team.map((user, idx) => (
|
||||
<TeamMemberComponent key={idx} index={idx} user={user} />
|
||||
{team.map(user => (
|
||||
<TeamMemberComponent key={user.userid} user={user} />
|
||||
))}
|
||||
{!!pendingInvites.length && (
|
||||
<div className="TeamForm-pending">
|
||||
|
|
|
@ -2,18 +2,17 @@ import React from 'react';
|
|||
import classnames from 'classnames';
|
||||
import { Icon } from 'antd';
|
||||
import { SOCIAL_INFO } from 'utils/social';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
import './TeamMember.less';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
user: TeamMember;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export default class CreateFlowTeamMember extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { user, index } = this.props;
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<div className="TeamMember">
|
||||
|
@ -21,11 +20,13 @@ export default class CreateFlowTeamMember extends React.PureComponent<Props> {
|
|||
<UserAvatar className="TeamMember-avatar-img" user={user} />
|
||||
</div>
|
||||
<div className="TeamMember-info">
|
||||
<div className="TeamMember-info-name">{user.name || <em>No name</em>}</div>
|
||||
<div className="TeamMember-info-name">
|
||||
{user.displayName || <em>No name</em>}
|
||||
</div>
|
||||
<div className="TeamMember-info-title">{user.title || <em>No title</em>}</div>
|
||||
<div className="TeamMember-info-social">
|
||||
{Object.values(SOCIAL_INFO).map(s => {
|
||||
const account = user.socialAccounts[s.type];
|
||||
const account = user.socialMedias.find(sm => s.service === sm.service);
|
||||
const cn = classnames(
|
||||
'TeamMember-info-social-icon',
|
||||
account && 'is-active',
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import lodash from 'lodash';
|
||||
import { Input, Form, Col, Row, Button, Icon, Alert } from 'antd';
|
||||
import { SOCIAL_INFO } from 'utils/social';
|
||||
import { SOCIAL_TYPE, TeamMember } from 'types';
|
||||
import { SOCIAL_SERVICE, User } from 'types';
|
||||
import { UserState } from 'modules/users/reducers';
|
||||
import { getCreateTeamMemberError } from 'modules/create/utils';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
|
@ -11,18 +11,18 @@ import './ProfileEdit.less';
|
|||
interface Props {
|
||||
user: UserState;
|
||||
onDone(): void;
|
||||
onEdit(user: TeamMember): void;
|
||||
onEdit(user: User): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
fields: TeamMember;
|
||||
fields: User;
|
||||
isChanged: boolean;
|
||||
showError: boolean;
|
||||
}
|
||||
|
||||
export default class ProfileEdit extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
fields: { ...this.props.user } as TeamMember,
|
||||
fields: { ...this.props.user } as User,
|
||||
isChanged: false,
|
||||
showError: false,
|
||||
};
|
||||
|
@ -48,7 +48,10 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
const { fields } = this.state;
|
||||
const error = getCreateTeamMemberError(fields);
|
||||
const isMissingField =
|
||||
!fields.name || !fields.title || !fields.emailAddress || !fields.ethAddress;
|
||||
!fields.displayName ||
|
||||
!fields.title ||
|
||||
!fields.emailAddress ||
|
||||
!fields.accountAddress;
|
||||
const isDisabled = !!error || isMissingField || !this.state.isChanged;
|
||||
|
||||
return (
|
||||
|
@ -62,11 +65,11 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
>
|
||||
<Icon
|
||||
className="ProfileEdit-avatar-change-icon"
|
||||
type={fields.avatarUrl ? 'picture' : 'plus-circle'}
|
||||
type={fields.avatar ? 'picture' : 'plus-circle'}
|
||||
/>
|
||||
<div>{fields.avatarUrl ? 'Change photo' : 'Add photo'}</div>
|
||||
<div>{fields.avatar ? 'Change photo' : 'Add photo'}</div>
|
||||
</Button>
|
||||
{fields.avatarUrl && (
|
||||
{fields.avatar && (
|
||||
<Button
|
||||
className="ProfileEdit-avatar-delete"
|
||||
icon="delete"
|
||||
|
@ -86,7 +89,7 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
name="name"
|
||||
autoComplete="off"
|
||||
placeholder="Display name (Required)"
|
||||
value={fields.name}
|
||||
value={fields.displayName}
|
||||
onChange={this.handleChangeField}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@ -115,29 +118,32 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
|
||||
<Form.Item>
|
||||
<Input
|
||||
name="ethAddress"
|
||||
name="accountAddress"
|
||||
disabled={true}
|
||||
autoComplete="ethAddress"
|
||||
autoComplete="accountAddress"
|
||||
placeholder="Ethereum address (Required)"
|
||||
value={fields.ethAddress}
|
||||
value={fields.accountAddress}
|
||||
onChange={this.handleChangeField}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={12}>
|
||||
{Object.values(SOCIAL_INFO).map(s => (
|
||||
<Col xs={24} sm={12} key={s.type}>
|
||||
<Form.Item>
|
||||
<Input
|
||||
placeholder={`${s.name} account`}
|
||||
autoComplete="off"
|
||||
value={fields.socialAccounts[s.type]}
|
||||
onChange={ev => this.handleSocialChange(ev, s.type)}
|
||||
addonBefore={s.icon}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
))}
|
||||
{Object.values(SOCIAL_INFO).map(s => {
|
||||
const field = fields.socialMedias.find(sm => sm.service === s.service);
|
||||
return (
|
||||
<Col xs={24} sm={12} key={s.service}>
|
||||
<Form.Item>
|
||||
<Input
|
||||
placeholder={`${s.name} account`}
|
||||
autoComplete="off"
|
||||
value={field ? field.username : ''}
|
||||
onChange={ev => this.handleSocialChange(ev, s.service)}
|
||||
addonBefore={s.icon}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
|
||||
{!isMissingField &&
|
||||
|
@ -205,20 +211,26 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
|
||||
private handleSocialChange = (
|
||||
ev: React.ChangeEvent<HTMLInputElement>,
|
||||
type: SOCIAL_TYPE,
|
||||
service: SOCIAL_SERVICE,
|
||||
) => {
|
||||
const { value } = ev.currentTarget;
|
||||
|
||||
// First remove...
|
||||
const socialMedias = this.state.fields.socialMedias.filter(
|
||||
sm => sm.service !== service,
|
||||
);
|
||||
if (value) {
|
||||
// Then re-add if there as a value
|
||||
socialMedias.push({
|
||||
service,
|
||||
username: value,
|
||||
});
|
||||
}
|
||||
|
||||
const fields = {
|
||||
...this.state.fields,
|
||||
socialAccounts: {
|
||||
...this.state.fields.socialAccounts,
|
||||
[type]: value,
|
||||
},
|
||||
socialMedias,
|
||||
};
|
||||
// delete key for empty string
|
||||
if (!value) {
|
||||
delete fields.socialAccounts[type];
|
||||
}
|
||||
const isChanged = this.isChangedCheck(fields);
|
||||
this.setState({
|
||||
isChanged,
|
||||
|
@ -232,7 +244,9 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
const num = Math.floor(Math.random() * 80);
|
||||
const fields = {
|
||||
...this.state.fields,
|
||||
avatarUrl: `https://randomuser.me/api/portraits/${gender}/${num}.jpg`,
|
||||
avatar: {
|
||||
image_url: `https://randomuser.me/api/portraits/${gender}/${num}.jpg`,
|
||||
},
|
||||
};
|
||||
const isChanged = this.isChangedCheck(fields);
|
||||
this.setState({
|
||||
|
@ -242,13 +256,15 @@ export default class ProfileEdit extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
private handleDeletePhoto = () => {
|
||||
const fields = lodash.clone(this.state.fields);
|
||||
delete fields.avatarUrl;
|
||||
const fields = {
|
||||
...this.state.fields,
|
||||
avatar: null,
|
||||
};
|
||||
const isChanged = this.isChangedCheck(fields);
|
||||
this.setState({ isChanged, fields });
|
||||
};
|
||||
|
||||
private isChangedCheck = (a: TeamMember) => {
|
||||
private isChangedCheck = (a: User) => {
|
||||
return !lodash.isEqual(a, this.props.user);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class ProfileInvite extends React.Component<Props, State> {
|
|||
const { invite } = this.props;
|
||||
const { isAccepting, isRejecting } = this.state;
|
||||
const { proposal } = invite;
|
||||
const inviter = proposal.team[0] || { name: 'Unknown user' };
|
||||
const inviter = proposal.team[0] || { displayName: 'Unknown user' };
|
||||
return (
|
||||
<div className="ProfileInvite">
|
||||
<div className="ProfileInvite-info">
|
||||
|
@ -52,7 +52,9 @@ class ProfileInvite extends React.Component<Props, State> {
|
|||
<div className="ProfileInvite-info-brief">
|
||||
{proposal.brief || <em>No description</em>}
|
||||
</div>
|
||||
<div className="ProfileInvite-info-inviter">created by {inviter.name}</div>
|
||||
<div className="ProfileInvite-info-inviter">
|
||||
created by {inviter.displayName}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ProfileInvite-actions">
|
||||
<Button
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class Profile extends React.Component<OwnProps> {
|
|||
<h3>Team</h3>
|
||||
<div className="ProfileProposal-block-team">
|
||||
{team.map(user => (
|
||||
<UserRow key={user.ethAddress || user.emailAddress} user={user} />
|
||||
<UserRow key={user.accountAddress || user.emailAddress} user={user} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button } from 'antd';
|
||||
import { SocialInfo } from 'types';
|
||||
import { SocialMedia } from 'types';
|
||||
import { usersActions } from 'modules/users';
|
||||
import { UserState } from 'modules/users/reducers';
|
||||
import { typedKeys } from 'utils/ts';
|
||||
import ProfileEdit from './ProfileEdit';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
import { SOCIAL_INFO, socialAccountToUrl } from 'utils/social';
|
||||
import { SOCIAL_INFO, socialMediaToUrl } from 'utils/social';
|
||||
import ShortAddress from 'components/ShortAddress';
|
||||
import './ProfileUser.less';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
@ -39,10 +38,10 @@ class ProfileUser extends React.Component<Props> {
|
|||
const {
|
||||
authUser,
|
||||
user,
|
||||
user: { socialAccounts },
|
||||
user: { socialMedias },
|
||||
} = this.props;
|
||||
|
||||
const isSelf = !!authUser && authUser.ethAddress === user.ethAddress;
|
||||
const isSelf = !!authUser && authUser.accountAddress === user.accountAddress;
|
||||
|
||||
if (this.state.isEditing) {
|
||||
return (
|
||||
|
@ -60,7 +59,7 @@ class ProfileUser extends React.Component<Props> {
|
|||
<UserAvatar className="ProfileUser-avatar-img" user={user} />
|
||||
</div>
|
||||
<div className="ProfileUser-info">
|
||||
<div className="ProfileUser-info-name">{user.name}</div>
|
||||
<div className="ProfileUser-info-name">{user.displayName}</div>
|
||||
<div className="ProfileUser-info-title">{user.title}</div>
|
||||
<div>
|
||||
{user.emailAddress && (
|
||||
|
@ -69,26 +68,18 @@ class ProfileUser extends React.Component<Props> {
|
|||
{user.emailAddress}
|
||||
</div>
|
||||
)}
|
||||
{user.ethAddress && (
|
||||
{user.accountAddress && (
|
||||
<div className="ProfileUser-info-address">
|
||||
<span>ethereum address</span>
|
||||
<ShortAddress address={user.ethAddress} />
|
||||
<ShortAddress address={user.accountAddress} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{Object.keys(socialAccounts).length > 0 && (
|
||||
{socialMedias.length > 0 && (
|
||||
<div className="ProfileUser-info-social">
|
||||
{typedKeys(SOCIAL_INFO).map(
|
||||
s =>
|
||||
(socialAccounts[s] && (
|
||||
<Social
|
||||
key={s}
|
||||
account={socialAccounts[s] as string}
|
||||
info={SOCIAL_INFO[s]}
|
||||
/>
|
||||
)) ||
|
||||
null,
|
||||
)}
|
||||
{socialMedias.map(sm => (
|
||||
<Social key={sm.service} socialMedia={sm} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{isSelf && (
|
||||
|
@ -104,10 +95,12 @@ class ProfileUser extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
const Social = ({ account, info }: { account: string; info: SocialInfo }) => {
|
||||
const Social = ({ socialMedia }: { socialMedia: SocialMedia }) => {
|
||||
return (
|
||||
<a href={socialAccountToUrl(account, info.type)}>
|
||||
<div className="ProfileUser-info-social-icon">{info.icon}</div>
|
||||
<a href={socialMediaToUrl(socialMedia)} target="_blank" rel="noopener nofollow">
|
||||
<div className="ProfileUser-info-social-icon">
|
||||
{SOCIAL_INFO[socialMedia.service].icon}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import { usersActions } from 'modules/users';
|
|||
import { AppState } from 'store/reducers';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'recompose';
|
||||
import { Spin, Tabs, Badge, message } from 'antd';
|
||||
import { Spin, Tabs, Badge } from 'antd';
|
||||
import HeaderDetails from 'components/HeaderDetails';
|
||||
import ProfileUser from './ProfileUser';
|
||||
import ProfileProposal from './ProfileProposal';
|
||||
|
@ -46,8 +46,8 @@ class Profile extends React.Component<Props> {
|
|||
const userLookupParam = this.props.match.params.id;
|
||||
const { authUser } = this.props;
|
||||
if (!userLookupParam) {
|
||||
if (authUser && authUser.ethAddress) {
|
||||
return <Redirect to={`/profile/${authUser.ethAddress}`} />;
|
||||
if (authUser && authUser.accountAddress) {
|
||||
return <Redirect to={`/profile/${authUser.accountAddress}`} />;
|
||||
} else {
|
||||
return <Redirect to="auth" />;
|
||||
}
|
||||
|
@ -56,7 +56,8 @@ class Profile extends React.Component<Props> {
|
|||
const user = this.props.usersMap[userLookupParam];
|
||||
const waiting = !user || !user.hasFetched;
|
||||
// TODO: Replace with userid checks
|
||||
const isAuthedUser = user && authUser && user.ethAddress === authUser.ethAddress;
|
||||
const isAuthedUser =
|
||||
user && authUser && user.accountAddress === authUser.accountAddress;
|
||||
|
||||
if (waiting) {
|
||||
return <Spin />;
|
||||
|
@ -77,9 +78,9 @@ class Profile extends React.Component<Props> {
|
|||
{/* TODO: SSR fetch user details */}
|
||||
{/* TODO: customize details for funders/creators */}
|
||||
<HeaderDetails
|
||||
title={`${user.name} is funding projects on Grant.io`}
|
||||
description={`Join ${user.name} in funding the future!`}
|
||||
image={user.avatarUrl}
|
||||
title={`${user.displayName} is funding projects on Grant.io`}
|
||||
description={`Join ${user.displayName} in funding the future!`}
|
||||
image={user.avatar ? user.avatar.image_url : undefined}
|
||||
/>
|
||||
<ProfileUser user={user} />
|
||||
<Tabs>
|
||||
|
@ -117,7 +118,11 @@ class Profile extends React.Component<Props> {
|
|||
<div>
|
||||
{noneCommented && <Placeholder subtitle="Has not made any comments yet" />}
|
||||
{comments.map(c => (
|
||||
<ProfileComment key={c.commentId} userName={user.name} comment={c} />
|
||||
<ProfileComment
|
||||
key={c.commentId}
|
||||
userName={user.displayName}
|
||||
comment={c}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
@ -137,7 +142,7 @@ class Profile extends React.Component<Props> {
|
|||
{invites.map(invite => (
|
||||
<ProfileInvite
|
||||
key={invite.id}
|
||||
userId={user.ethAddress}
|
||||
userId={user.accountAddress}
|
||||
invite={invite}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -10,7 +10,7 @@ interface Props {
|
|||
const TeamBlock = ({ proposal }: Props) => {
|
||||
let content;
|
||||
if (proposal) {
|
||||
content = proposal.team.map(user => <UserRow key={user.name} user={user} />);
|
||||
content = proposal.team.map(user => <UserRow key={user.displayName} user={user} />);
|
||||
} else {
|
||||
content = <Spin />;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,8 @@ export class ProposalCard extends React.Component<Props> {
|
|||
|
||||
<div className="ProposalCard-team">
|
||||
<div className="ProposalCard-team-name">
|
||||
{team[0].name} {team.length > 1 && <small>+{team.length - 1} other</small>}
|
||||
{team[0].displayName}{' '}
|
||||
{team.length > 1 && <small>+{team.length - 1} other</small>}
|
||||
</div>
|
||||
<div className="ProposalCard-team-avatars">
|
||||
{[...team].reverse().map((u, idx) => (
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import React from 'react';
|
||||
import Identicon from 'components/Identicon';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
import defaultUserImg from 'static/images/default-user.jpg';
|
||||
|
||||
interface Props {
|
||||
user: TeamMember;
|
||||
user: User;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const UserAvatar: React.SFC<Props> = ({ user, className }) => {
|
||||
if (user.avatarUrl) {
|
||||
return <img className={className} src={user.avatarUrl} />;
|
||||
} else if (user.ethAddress) {
|
||||
return <Identicon className={className} address={user.ethAddress} />;
|
||||
if (user.avatar && user.avatar.image_url) {
|
||||
return <img className={className} src={user.avatar.image_url} />;
|
||||
} else if (user.accountAddress) {
|
||||
return <Identicon className={className} address={user.accountAddress} />;
|
||||
} else {
|
||||
return <img className={className} src={defaultUserImg} />;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './style.less';
|
||||
|
||||
interface Props {
|
||||
user: TeamMember;
|
||||
user: User;
|
||||
}
|
||||
|
||||
const UserRow = ({ user }: Props) => (
|
||||
<Link to={`/profile/${user.ethAddress || user.emailAddress}`} className="UserRow">
|
||||
<Link to={`/profile/${user.accountAddress || user.emailAddress}`} className="UserRow">
|
||||
<div className="UserRow-avatar">
|
||||
<UserAvatar user={user} className="UserRow-avatar-img" />
|
||||
</div>
|
||||
<div className="UserRow-info">
|
||||
<div className="UserRow-info-main">{user.name}</div>
|
||||
<div className="UserRow-info-main">{user.displayName}</div>
|
||||
<p className="UserRow-info-secondary">{user.title}</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import types from './types';
|
||||
// TODO: Use a common User type instead of this
|
||||
import { TeamMember, AuthSignatureData } from 'types';
|
||||
import { User, AuthSignatureData } from 'types';
|
||||
|
||||
export interface AuthState {
|
||||
user: TeamMember | null;
|
||||
user: User | null;
|
||||
isAuthingUser: boolean;
|
||||
authUserError: string | null;
|
||||
|
||||
checkedUsers: { [address: string]: TeamMember | false };
|
||||
checkedUsers: { [address: string]: User | false };
|
||||
isCheckingUser: boolean;
|
||||
|
||||
isCreatingUser: boolean;
|
||||
|
@ -53,7 +53,7 @@ export default function createReducer(
|
|||
...state,
|
||||
user: action.payload.user,
|
||||
authSignature: action.payload.authSignature, // TODO: Make this the real token
|
||||
authSignatureAddress: action.payload.user.ethAddress,
|
||||
authSignatureAddress: action.payload.user.accountAddress,
|
||||
isAuthingUser: false,
|
||||
};
|
||||
case types.AUTH_USER_REJECTED:
|
||||
|
@ -74,7 +74,7 @@ export default function createReducer(
|
|||
...state,
|
||||
user: action.payload.user,
|
||||
authSignature: action.payload.authSignature,
|
||||
authSignatureAddress: action.payload.user.ethAddress,
|
||||
authSignatureAddress: action.payload.user.accountAddress,
|
||||
isCreatingUser: false,
|
||||
checkedUsers: {
|
||||
...state.checkedUsers,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ProposalDraft, CreateMilestone } from 'types';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
import { isValidEthAddress, getAmountError } from 'utils/validators';
|
||||
import { MILESTONE_STATE, ProposalWithCrowdFund } from 'types';
|
||||
import { ProposalContractData } from 'modules/web3/actions';
|
||||
|
@ -153,7 +153,7 @@ export function getCreateErrors(
|
|||
if (team) {
|
||||
let didTeamError = false;
|
||||
const teamErrors = team.map(u => {
|
||||
if (!u.name || !u.title || !u.emailAddress || !u.ethAddress) {
|
||||
if (!u.displayName || !u.title || !u.emailAddress || !u.accountAddress) {
|
||||
didTeamError = true;
|
||||
return '';
|
||||
}
|
||||
|
@ -170,14 +170,14 @@ export function getCreateErrors(
|
|||
return errors;
|
||||
}
|
||||
|
||||
export function getCreateTeamMemberError(user: TeamMember) {
|
||||
if (user.name.length > 30) {
|
||||
export function getCreateTeamMemberError(user: User) {
|
||||
if (user.displayName.length > 30) {
|
||||
return 'Display name can only be 30 characters maximum';
|
||||
} else if (user.title.length > 30) {
|
||||
return 'Title can only be 30 characters maximum';
|
||||
} else if (!/.+\@.+\..+/.test(user.emailAddress)) {
|
||||
return 'That doesn’t look like a valid email address';
|
||||
} else if (!isValidEthAddress(user.ethAddress)) {
|
||||
} else if (!isValidEthAddress(user.accountAddress)) {
|
||||
return 'That doesn’t look like a valid ETH address';
|
||||
}
|
||||
|
||||
|
@ -235,6 +235,7 @@ export function makeProposalPreviewFromDraft(
|
|||
proposalAddress: '0x0',
|
||||
dateCreated: Date.now(),
|
||||
title: draft.title,
|
||||
brief: draft.brief,
|
||||
content: draft.content,
|
||||
stage: 'preview',
|
||||
category: draft.category || PROPOSAL_CATEGORY.DAPP,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserProposal, UserComment, TeamMember } from 'types';
|
||||
import { UserProposal, UserComment, User } from 'types';
|
||||
import types from './types';
|
||||
import {
|
||||
getUser,
|
||||
|
@ -28,7 +28,7 @@ export function fetchUser(userFetchId: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export function updateUser(user: TeamMember) {
|
||||
export function updateUser(user: User) {
|
||||
const userClone = cleanClone(INITIAL_TEAM_MEMBER_STATE, user);
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
dispatch({ type: types.UPDATE_USER_PENDING, payload: { user } });
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import lodash from 'lodash';
|
||||
import { UserProposal, UserComment, TeamInviteWithProposal } from 'types';
|
||||
import types from './types';
|
||||
import { TeamMember } from 'types';
|
||||
import { User } from 'types';
|
||||
|
||||
export interface TeamInviteWithResponse extends TeamInviteWithProposal {
|
||||
isResponding: boolean;
|
||||
respondError: number | null;
|
||||
}
|
||||
|
||||
export interface UserState extends TeamMember {
|
||||
export interface UserState extends User {
|
||||
isFetching: boolean;
|
||||
hasFetched: boolean;
|
||||
fetchError: number | null;
|
||||
|
@ -36,12 +36,13 @@ export interface UsersState {
|
|||
map: { [index: string]: UserState };
|
||||
}
|
||||
|
||||
export const INITIAL_TEAM_MEMBER_STATE: TeamMember = {
|
||||
ethAddress: '',
|
||||
avatarUrl: '',
|
||||
name: '',
|
||||
export const INITIAL_TEAM_MEMBER_STATE: User = {
|
||||
userid: 0,
|
||||
accountAddress: '',
|
||||
avatar: null,
|
||||
displayName: '',
|
||||
emailAddress: '',
|
||||
socialAccounts: {},
|
||||
socialMedias: [],
|
||||
title: '',
|
||||
};
|
||||
|
||||
|
@ -105,19 +106,19 @@ export default (state = INITIAL_STATE, action: any) => {
|
|||
});
|
||||
// update
|
||||
case types.UPDATE_USER_PENDING:
|
||||
return updateUserState(state, payload.user.ethAddress, {
|
||||
return updateUserState(state, payload.user.accountAddress, {
|
||||
isUpdating: true,
|
||||
updateError: null,
|
||||
});
|
||||
case types.UPDATE_USER_FULFILLED:
|
||||
return updateUserState(
|
||||
state,
|
||||
payload.user.ethAddress,
|
||||
payload.user.accountAddress,
|
||||
{ isUpdating: false },
|
||||
payload.user,
|
||||
);
|
||||
case types.UPDATE_USER_REJECTED:
|
||||
return updateUserState(state, payload.user.ethAddress, {
|
||||
return updateUserState(state, payload.user.accountAddress, {
|
||||
isUpdating: false,
|
||||
updateError: errorStatus,
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
|||
class ProfilePage extends React.Component<Props> {
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
return <h1>Settings for {user && user.name}</h1>;
|
||||
return <h1>Settings for {user && user.displayName}</h1>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,11 @@
|
|||
import { TeamMember } from 'types';
|
||||
import { socialAccountsToUrls, socialUrlsToAccounts } from 'utils/social';
|
||||
import { User } from 'types';
|
||||
import { socialMediaToUrl } from 'utils/social';
|
||||
|
||||
export function formatTeamMemberForPost(user: TeamMember) {
|
||||
export function formatUserForPost(user: User) {
|
||||
return {
|
||||
displayName: user.name,
|
||||
title: user.title,
|
||||
accountAddress: user.ethAddress,
|
||||
emailAddress: user.emailAddress,
|
||||
avatar: user.avatarUrl ? { link: user.avatarUrl } : {},
|
||||
socialMedias: socialAccountsToUrls(user.socialAccounts).map(url => ({
|
||||
link: url,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatTeamMemberFromGet(user: any): TeamMember {
|
||||
return {
|
||||
name: user.displayName,
|
||||
title: user.title,
|
||||
ethAddress: user.accountAddress,
|
||||
emailAddress: user.emailAddress,
|
||||
avatarUrl: user.avatar && user.avatar.imageUrl,
|
||||
socialAccounts: socialUrlsToAccounts(
|
||||
user.socialMedias.map((sm: any) => sm.socialMediaLink),
|
||||
),
|
||||
...user,
|
||||
avatar: user.avatar ? user.avatar.image_url : null,
|
||||
socialMedias: user.socialMedias.map(socialMediaToUrl),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,60 +1,36 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'antd';
|
||||
import keybaseIcon from 'static/images/keybase.svg';
|
||||
import { SOCIAL_TYPE, SocialAccountMap, SocialInfo } from 'types';
|
||||
import { SOCIAL_SERVICE, SocialMedia, SocialInfo } from 'types';
|
||||
|
||||
const accountNameRegex = '([a-zA-Z0-9-_]*)';
|
||||
export const SOCIAL_INFO: { [key in SOCIAL_TYPE]: SocialInfo } = {
|
||||
[SOCIAL_TYPE.GITHUB]: {
|
||||
type: SOCIAL_TYPE.GITHUB,
|
||||
export const SOCIAL_INFO: { [key in SOCIAL_SERVICE]: SocialInfo } = {
|
||||
[SOCIAL_SERVICE.GITHUB]: {
|
||||
service: SOCIAL_SERVICE.GITHUB,
|
||||
name: 'Github',
|
||||
format: `https://github.com/${accountNameRegex}`,
|
||||
icon: <Icon type="github" />,
|
||||
},
|
||||
[SOCIAL_TYPE.TWITTER]: {
|
||||
type: SOCIAL_TYPE.TWITTER,
|
||||
[SOCIAL_SERVICE.TWITTER]: {
|
||||
service: SOCIAL_SERVICE.TWITTER,
|
||||
name: 'Twitter',
|
||||
format: `https://twitter.com/${accountNameRegex}`,
|
||||
icon: <Icon type="twitter" />,
|
||||
},
|
||||
[SOCIAL_TYPE.LINKEDIN]: {
|
||||
type: SOCIAL_TYPE.LINKEDIN,
|
||||
[SOCIAL_SERVICE.LINKEDIN]: {
|
||||
service: SOCIAL_SERVICE.LINKEDIN,
|
||||
name: 'LinkedIn',
|
||||
format: `https://linkedin.com/in/${accountNameRegex}`,
|
||||
icon: <Icon type="linkedin" />,
|
||||
},
|
||||
[SOCIAL_TYPE.KEYBASE]: {
|
||||
type: SOCIAL_TYPE.KEYBASE,
|
||||
[SOCIAL_SERVICE.KEYBASE]: {
|
||||
service: SOCIAL_SERVICE.KEYBASE,
|
||||
name: 'KeyBase',
|
||||
format: `https://keybase.io/${accountNameRegex}`,
|
||||
icon: <Icon component={keybaseIcon} />,
|
||||
},
|
||||
};
|
||||
|
||||
function urlToAccount(format: string, url: string): string | false {
|
||||
const matches = url.match(new RegExp(format));
|
||||
return matches && matches[1] ? matches[1] : false;
|
||||
}
|
||||
|
||||
export function socialAccountToUrl(account: string, type: SOCIAL_TYPE): string {
|
||||
return SOCIAL_INFO[type].format.replace(accountNameRegex, account);
|
||||
}
|
||||
|
||||
export function socialUrlsToAccounts(urls: string[]): SocialAccountMap {
|
||||
const accounts: SocialAccountMap = {};
|
||||
urls.forEach(url => {
|
||||
Object.values(SOCIAL_INFO).forEach(s => {
|
||||
const account = urlToAccount(s.format, url);
|
||||
if (account) {
|
||||
accounts[s.type] = account;
|
||||
}
|
||||
});
|
||||
});
|
||||
return accounts;
|
||||
}
|
||||
|
||||
export function socialAccountsToUrls(accounts: SocialAccountMap): string[] {
|
||||
return Object.entries(accounts).map(([key, value]) => {
|
||||
return socialAccountToUrl(value as string, key as SOCIAL_TYPE);
|
||||
});
|
||||
export function socialMediaToUrl(sm: SocialMedia): string {
|
||||
return SOCIAL_INFO[sm.service].format.replace(accountNameRegex, sm.username);
|
||||
}
|
||||
|
|
|
@ -2,20 +2,31 @@ import * as React from 'react';
|
|||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { DONATION } from 'utils/constants';
|
||||
import { User } from 'types';
|
||||
|
||||
import 'components/UserRow/style.less';
|
||||
import UserRow from 'components/UserRow';
|
||||
|
||||
const user = {
|
||||
name: 'Dana Hayes',
|
||||
const user: User = {
|
||||
userid: 123,
|
||||
displayName: 'Dana Hayes',
|
||||
title: 'QA Engineer',
|
||||
avatarUrl: 'https://randomuser.me/api/portraits/women/19.jpg',
|
||||
ethAddress: DONATION.ETH,
|
||||
avatar: {
|
||||
image_url: 'https://randomuser.me/api/portraits/women/19.jpg',
|
||||
},
|
||||
accountAddress: DONATION.ETH,
|
||||
emailAddress: 'test@test.test',
|
||||
socialAccounts: {},
|
||||
socialMedias: [],
|
||||
};
|
||||
|
||||
const cases = [
|
||||
interface Case {
|
||||
disp: string;
|
||||
props: {
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
const cases: Case[] = [
|
||||
{
|
||||
disp: 'Full User',
|
||||
props: {
|
||||
|
@ -29,7 +40,7 @@ const cases = [
|
|||
props: {
|
||||
user: {
|
||||
...user,
|
||||
avatarUrl: '',
|
||||
avatar: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -38,8 +49,8 @@ const cases = [
|
|||
props: {
|
||||
user: {
|
||||
...user,
|
||||
avatarUrl: '',
|
||||
ethAddress: '',
|
||||
avatar: null,
|
||||
accountAddress: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -48,7 +59,7 @@ const cases = [
|
|||
props: {
|
||||
user: {
|
||||
...user,
|
||||
name: 'Dr. Baron Longnamivitch von Testeronomous III Esq.',
|
||||
displayName: 'Dr. Baron Longnamivitch von Testeronomous III Esq.',
|
||||
title: 'Amazing person, all around cool neat-o guy, 10/10 would order again',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -161,33 +161,37 @@ export function getProposalWithCrowdFund({
|
|||
proposalAddress: '0x033fDc6C01DC2385118C7bAAB88093e22B8F0710',
|
||||
dateCreated: created / 1000,
|
||||
title: 'Crowdfund Title',
|
||||
brief: 'A cool test crowdfund',
|
||||
content: 'body',
|
||||
stage: 'FUNDING_REQUIRED',
|
||||
category: PROPOSAL_CATEGORY.COMMUNITY,
|
||||
team: [
|
||||
{
|
||||
name: 'Test Proposer',
|
||||
userid: 123,
|
||||
displayName: 'Test Proposer',
|
||||
title: '',
|
||||
ethAddress: '0x0c7C6178AD0618Bf289eFd5E1Ff9Ada25fC3bDE7',
|
||||
accountAddress: '0x0c7C6178AD0618Bf289eFd5E1Ff9Ada25fC3bDE7',
|
||||
emailAddress: '',
|
||||
avatarUrl: '',
|
||||
socialAccounts: {},
|
||||
avatar: null,
|
||||
socialMedias: [],
|
||||
},
|
||||
{
|
||||
name: 'Test Proposer',
|
||||
userid: 456,
|
||||
displayName: 'Test Proposer',
|
||||
title: '',
|
||||
ethAddress: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
accountAddress: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
||||
emailAddress: '',
|
||||
avatarUrl: '',
|
||||
socialAccounts: {},
|
||||
avatar: null,
|
||||
socialMedias: [],
|
||||
},
|
||||
{
|
||||
name: 'Test Proposer',
|
||||
userid: 789,
|
||||
displayName: 'Test Proposer',
|
||||
title: '',
|
||||
ethAddress: '0x529104532a9779ea9eae0c1e325b3368e0f8add4',
|
||||
accountAddress: '0x529104532a9779ea9eae0c1e325b3368e0f8add4',
|
||||
emailAddress: '',
|
||||
avatarUrl: '',
|
||||
socialAccounts: {},
|
||||
avatar: null,
|
||||
socialMedias: [],
|
||||
},
|
||||
],
|
||||
milestones,
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
CreateMilestone,
|
||||
ProposalMilestone,
|
||||
Update,
|
||||
TeamMember,
|
||||
User,
|
||||
Milestone,
|
||||
Comment,
|
||||
} from 'types';
|
||||
|
@ -57,7 +57,7 @@ export interface ProposalDraft {
|
|||
deadlineDuration: number;
|
||||
voteDuration: number;
|
||||
milestones: CreateMilestone[];
|
||||
team: TeamMember[];
|
||||
team: User[];
|
||||
invites: TeamInvite[];
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ export interface Proposal {
|
|||
stage: string;
|
||||
category: PROPOSAL_CATEGORY;
|
||||
milestones: ProposalMilestone[];
|
||||
team: TeamMember[];
|
||||
team: User[];
|
||||
}
|
||||
|
||||
export interface ProposalWithCrowdFund extends Proposal {
|
||||
|
@ -99,7 +99,7 @@ export interface UserProposal {
|
|||
proposalId: number;
|
||||
title: string;
|
||||
brief: string;
|
||||
team: TeamMember[];
|
||||
team: User[];
|
||||
funded: Wei;
|
||||
target: Wei;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
export type SocialAccountMap = Partial<{ [key in SOCIAL_TYPE]: string }>;
|
||||
export type SocialAccountMap = Partial<{ [key in SOCIAL_SERVICE]: string }>;
|
||||
|
||||
export interface SocialMedia {
|
||||
service: SOCIAL_SERVICE;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface SocialInfo {
|
||||
type: SOCIAL_TYPE;
|
||||
service: SOCIAL_SERVICE;
|
||||
name: string;
|
||||
format: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
export enum SOCIAL_TYPE {
|
||||
export enum SOCIAL_SERVICE {
|
||||
GITHUB = 'GITHUB',
|
||||
TWITTER = 'TWITTER',
|
||||
LINKEDIN = 'LINKEDIN',
|
||||
|
|
|
@ -1,21 +1,11 @@
|
|||
import { SocialAccountMap } from 'types';
|
||||
import { SocialMedia } from 'types';
|
||||
|
||||
export interface User {
|
||||
userid: number;
|
||||
accountAddress: string;
|
||||
userid: number | string;
|
||||
username: string;
|
||||
emailAddress: string; // TODO: Split into full user type
|
||||
displayName: string;
|
||||
title: string;
|
||||
avatar: {
|
||||
'120x120': string;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Merge this or extend the `User` type in proposals/reducers.ts
|
||||
export interface TeamMember {
|
||||
name: string;
|
||||
title: string;
|
||||
avatarUrl: string;
|
||||
ethAddress: string;
|
||||
emailAddress: string;
|
||||
socialAccounts: SocialAccountMap;
|
||||
socialMedias: SocialMedia[];
|
||||
avatar: { image_url: string } | null;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue