Skip to content

Commit

Permalink
user: Show custom profile fields in a user's profile
Browse files Browse the repository at this point in the history
Fixes: #2900
  • Loading branch information
gnprice committed Jun 8, 2022
1 parent a2113af commit 0a10831
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/account-info/AccountDetailsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ActivityText from '../title/ActivityText';
import { doNarrow } from '../actions';
import { getUserIsActive, getUserForId } from '../users/userSelectors';
import { nowInTimeZone } from '../utils/date';
import CustomProfileFields from './CustomProfileFields';

const styles = createStyleSheet({
pmButton: {
Expand Down Expand Up @@ -81,6 +82,9 @@ export default function AccountDetailsScreen(props: Props): Node {
<ZulipText style={globalStyles.largerText} text={localTime} />
</View>
)}
<View style={styles.itemWrapper}>
<CustomProfileFields user={user} />
</View>
{!isActive && (
<ZulipTextIntl style={styles.deactivatedText} text="(This user has been deactivated)" />
)}
Expand Down
133 changes: 133 additions & 0 deletions src/account-info/CustomProfileFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// @flow strict-local
import * as React from 'react';
import { View } from 'react-native';

import { type UserOrBot, type UserId } from '../api/modelTypes';
import WebLink from '../common/WebLink';
import ZulipText from '../common/ZulipText';
import ZulipTextIntl from '../common/ZulipTextIntl';
import { ensureUnreachable } from '../generics';
import { useSelector } from '../react-redux';
import { tryGetUserForId } from '../selectors';
import {
type CustomProfileFieldValue,
getCustomProfileFieldsForUser,
} from '../users/userSelectors';
import UserItem from '../users/UserItem';
import { useNavigation } from '../react-navigation';
import { navigateToAccountDetails } from '../nav/navActions';

/* eslint-disable no-shadow */

type Props = {|
+user: UserOrBot,
|};

function CustomProfileFieldUser(props: {| +userId: UserId |}): React.Node {
const { userId } = props;
const user = useSelector(state => tryGetUserForId(state, userId));

const navigation = useNavigation();
const onPress = React.useCallback(
(user: UserOrBot) => {
navigation.dispatch(navigateToAccountDetails(user.user_id));
},
[navigation],
);

if (!user) {
return <ZulipTextIntl text="(unknown user)" />;
}

return <UserItem userId={userId} onPress={onPress} size="medium" />;
}

function CustomProfileFieldRow(props: {|
+name: string,
+value: CustomProfileFieldValue,
+first: boolean,
|}): React.Node {
const { first, name, value } = props;

const styles = React.useMemo(
() => ({
row: { marginTop: first ? 0 : 8, flexDirection: 'row' },
label: { width: 96, fontWeight: 'bold' },
valueView: { flex: 1, paddingStart: 8 },
valueText: { flex: 1, paddingStart: 8 },
// The padding difference compensates for the paddingHorizontal in UserItem.
valueUnpadded: { flex: 1 },
}),
[first],
);

let valueElement = undefined;
switch (value.displayType) {
case 'text':
valueElement = <ZulipText style={styles.valueText} text={value.text} />;
break;

case 'link':
valueElement = (
<View style={styles.valueView}>
{value.url ? (
<WebLink url={value.url} label={{ text: '{_}', values: { _: value.text } }} />
) : (
<ZulipText text={value.text} />
)}
</View>
);
break;

case 'users':
valueElement = (
<View style={styles.valueUnpadded}>
{value.userIds.map(userId => (
<CustomProfileFieldUser key={userId} userId={userId} />
))}
</View>
);
break;

default:
ensureUnreachable(value.displayType);
return null;
}

return (
<View style={styles.row}>
<ZulipText style={styles.label} text={name} />
{valueElement}
</View>
);
}

export default function CustomProfileFields(props: Props): React.Node {
const { user } = props;
const realm = useSelector(state => state.realm);

const fields = React.useMemo(() => getCustomProfileFieldsForUser(realm, user), [realm, user]);

const styles = React.useMemo(
() => ({
outer: { flexDirection: 'row', justifyContent: 'center' },
inner: { flexBasis: 400, flexShrink: 1 },
}),
[],
);

return (
<View style={styles.outer}>
<View style={styles.inner}>
{fields.map((field, i) => (
<CustomProfileFieldRow
key={field.fieldId}
name={field.name}
value={field.value}
first={i === 0}
/>
))}
</View>
</View>
);
}
1 change: 1 addition & 0 deletions static/translations/messages_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
"Send private message": "Send private message",
"View private messages": "View private messages",
"(This user has been deactivated)": "(This user has been deactivated)",
"(unknown user)": "(unknown user)",
"Forgot password?": "Forgot password?",
"Members": "Members",
"Recipients": "Recipients",
Expand Down

0 comments on commit 0a10831

Please sign in to comment.