Rework profile header
parent
cb310ab1c1
commit
2ec09ba545
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -2,19 +2,22 @@ import React, {useState, useEffect} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Button,
|
|
||||||
Image,
|
Image,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {ProfileViewModel} from '../../../state/models/profile-view'
|
import {ProfileViewModel} from '../../../state/models/profile-view'
|
||||||
import {useStores} from '../../../state'
|
import {useStores} from '../../../state'
|
||||||
import {pluralize} from '../../lib/strings'
|
import {pluralize} from '../../lib/strings'
|
||||||
import {s, colors} from '../../lib/styles'
|
import {s, gradients, colors} from '../../lib/styles'
|
||||||
import {AVIS} from '../../lib/assets'
|
import {AVIS, BANNER} from '../../lib/assets'
|
||||||
import Toast from '../util/Toast'
|
import Toast from '../util/Toast'
|
||||||
|
import {Link} from '../util/Link'
|
||||||
|
import {Selector, SelectorItem} from '../util/Selector'
|
||||||
|
|
||||||
export const ProfileHeader = observer(function ProfileHeader({
|
export const ProfileHeader = observer(function ProfileHeader({
|
||||||
user,
|
user,
|
||||||
|
@ -35,6 +38,11 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
newView.setup().catch(err => console.error('Failed to fetch profile', err))
|
newView.setup().catch(err => console.error('Failed to fetch profile', err))
|
||||||
}, [user, view?.params.user, store])
|
}, [user, view?.params.user, store])
|
||||||
|
|
||||||
|
const selectorItems: SelectorItem[] = [
|
||||||
|
{label: 'Posts', onSelect() {}},
|
||||||
|
{label: 'Badges', onSelect() {}},
|
||||||
|
]
|
||||||
|
|
||||||
const onPressToggleFollow = () => {
|
const onPressToggleFollow = () => {
|
||||||
view?.toggleFollowing().then(
|
view?.toggleFollowing().then(
|
||||||
() => {
|
() => {
|
||||||
|
@ -51,6 +59,12 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
err => console.error('Failed to toggle follow', err),
|
err => console.error('Failed to toggle follow', err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const onPressEditProfile = () => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
const onPressMenu = () => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
const onPressFollowers = () => {
|
const onPressFollowers = () => {
|
||||||
store.nav.navigate(`/profile/${user}/followers`)
|
store.nav.navigate(`/profile/${user}/followers`)
|
||||||
}
|
}
|
||||||
|
@ -84,17 +98,55 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
|
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
|
const isMe = store.me.did === view.did
|
||||||
return (
|
return (
|
||||||
<View style={styles.outer}>
|
<View style={styles.outer}>
|
||||||
|
<Image style={styles.banner} source={BANNER} />
|
||||||
<Image style={styles.avi} source={AVIS[view.name] || AVIS['alice.com']} />
|
<Image style={styles.avi} source={AVIS[view.name] || AVIS['alice.com']} />
|
||||||
<View style={[styles.nameLine, s.mb2]}>
|
<View style={styles.content}>
|
||||||
<Text style={[s.bold, s.f18, s.mr2]}>{view.displayName}</Text>
|
<View style={[styles.displayNameLine]}>
|
||||||
<Text style={[s.gray5]}>@{view.name}</Text>
|
<Text style={styles.displayName}>{view.displayName}</Text>
|
||||||
</View>
|
</View>
|
||||||
{view.description && (
|
<View style={styles.badgesLine}>
|
||||||
<Text style={[s.mb5, s.f15, s['lh15-1.3']]}>{view.description}</Text>
|
<FontAwesomeIcon icon="shield" style={s.mr5} size={12} />
|
||||||
|
<Link href="/" title="Badge TODO">
|
||||||
|
<Text style={[s.f12, s.bold]}>
|
||||||
|
Employee <Text style={[s.blue3]}>@blueskyweb.xyz</Text>
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.buttonsLine]}>
|
||||||
|
{isMe ? (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onPressEditProfile}
|
||||||
|
style={[styles.mainBtn, styles.btn]}>
|
||||||
|
<Text style={[s.fw600, s.f16]}>Edit Profile</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : view.myState.hasFollowed ? (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onPressToggleFollow}
|
||||||
|
style={[styles.mainBtn, styles.btn]}>
|
||||||
|
<Text style={[s.fw600, s.f16]}>Following</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<TouchableOpacity onPress={onPressToggleFollow}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[gradients.primary.start, gradients.primary.end]}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={[styles.followBtn]}>
|
||||||
|
<FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} />
|
||||||
|
<Text style={[s.white, s.fw600, s.f16]}>Follow</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
<View style={s.flexRow}>
|
<TouchableOpacity
|
||||||
|
onPress={onPressMenu}
|
||||||
|
style={[styles.btn, styles.secondaryBtn, s.ml10]}>
|
||||||
|
<FontAwesomeIcon icon="ellipsis" style={[s.gray5]} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<View style={[s.flexRow, s.mb10]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[s.flexRow, s.mr10]}
|
style={[s.flexRow, s.mr10]}
|
||||||
onPress={onPressFollowers}>
|
onPress={onPressFollowers}>
|
||||||
|
@ -103,7 +155,9 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
{pluralize(view.followersCount, 'follower')}
|
{pluralize(view.followersCount, 'follower')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity style={[s.flexRow, s.mr10]} onPress={onPressFollows}>
|
<TouchableOpacity
|
||||||
|
style={[s.flexRow, s.mr10]}
|
||||||
|
onPress={onPressFollows}>
|
||||||
<Text style={[s.bold, s.mr2]}>{view.followsCount}</Text>
|
<Text style={[s.bold, s.mr2]}>{view.followsCount}</Text>
|
||||||
<Text style={s.gray5}>following</Text>
|
<Text style={s.gray5}>following</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
@ -112,12 +166,11 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
<Text style={s.gray5}>{pluralize(view.postsCount, 'post')}</Text>
|
<Text style={s.gray5}>{pluralize(view.postsCount, 'post')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
{view.description && (
|
||||||
<Button
|
<Text style={[s.mb10, s.f15, s['lh15-1.3']]}>{view.description}</Text>
|
||||||
title={view.myState.hasFollowed ? 'Unfollow' : 'Follow'}
|
)}
|
||||||
onPress={onPressToggleFollow}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
<Selector items={selectorItems} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -125,18 +178,66 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: {
|
outer: {
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.white,
|
||||||
padding: 10,
|
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderColor: colors.gray2,
|
borderColor: colors.gray2,
|
||||||
},
|
},
|
||||||
avi: {
|
banner: {
|
||||||
width: 60,
|
width: '100%',
|
||||||
height: 60,
|
height: 120,
|
||||||
borderRadius: 30,
|
|
||||||
resizeMode: 'cover',
|
|
||||||
},
|
},
|
||||||
nameLine: {
|
avi: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 80,
|
||||||
|
left: 10,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 40,
|
||||||
|
resizeMode: 'cover',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: colors.white,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingTop: 8,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
paddingBottom: 4,
|
||||||
|
},
|
||||||
|
displayNameLine: {
|
||||||
|
paddingLeft: 86,
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
badgesLine: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'flex-end',
|
alignItems: 'center',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
buttonsLine: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
followBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 60,
|
||||||
|
borderRadius: 30,
|
||||||
|
},
|
||||||
|
btn: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 30,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.gray2,
|
||||||
|
},
|
||||||
|
mainBtn: {
|
||||||
|
paddingHorizontal: 40,
|
||||||
|
},
|
||||||
|
secondaryBtn: {
|
||||||
|
paddingHorizontal: 12,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React, {useState} from 'react'
|
||||||
|
import {
|
||||||
|
StyleProp,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native'
|
||||||
|
import {colors} from '../../lib/styles'
|
||||||
|
|
||||||
|
export interface SelectorItem {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Selector({
|
||||||
|
style,
|
||||||
|
items,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
items: SelectorItem[]
|
||||||
|
onSelect?: (index: number) => void
|
||||||
|
}) {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
||||||
|
const onPressItem = (index: number) => {
|
||||||
|
setSelectedIndex(index)
|
||||||
|
onSelect?.(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.outer, style]}>
|
||||||
|
{items.map((item, i) => {
|
||||||
|
const selected = i === selectedIndex
|
||||||
|
return (
|
||||||
|
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
|
||||||
|
<View style={selected ? styles.itemSelected : styles.item}>
|
||||||
|
<Text style={selected ? styles.labelSelected : styles.label}>
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
outer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
paddingBottom: 12,
|
||||||
|
marginRight: 20,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.gray5,
|
||||||
|
},
|
||||||
|
itemSelected: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
marginRight: 20,
|
||||||
|
borderBottomWidth: 4,
|
||||||
|
borderBottomColor: colors.purple3,
|
||||||
|
},
|
||||||
|
labelSelected: {
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
})
|
|
@ -26,6 +26,7 @@ import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
|
||||||
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
||||||
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
||||||
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
||||||
|
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
|
||||||
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
||||||
import {faUser} from '@fortawesome/free-regular-svg-icons/faUser'
|
import {faUser} from '@fortawesome/free-regular-svg-icons/faUser'
|
||||||
import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers'
|
import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers'
|
||||||
|
@ -60,6 +61,7 @@ export function setup() {
|
||||||
faRetweet,
|
faRetweet,
|
||||||
faShare,
|
faShare,
|
||||||
faShareFromSquare,
|
faShareFromSquare,
|
||||||
|
faShield,
|
||||||
faUser,
|
faUser,
|
||||||
faUsers,
|
faUsers,
|
||||||
faX,
|
faX,
|
||||||
|
|
|
@ -5,3 +5,5 @@ export const AVIS: Record<string, ImageSourcePropType> = {
|
||||||
'bob.com': require('../../../public/img/bob.jpg'),
|
'bob.com': require('../../../public/img/bob.jpg'),
|
||||||
'carla.com': require('../../../public/img/carla.jpg'),
|
'carla.com': require('../../../public/img/carla.jpg'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BANNER: ImageSourcePropType = require('../../../public/img/banner.jpg')
|
||||||
|
|
|
@ -5,3 +5,5 @@ export const AVIS: Record<string, ImageSourcePropType> = {
|
||||||
'bob.com': {uri: '/img/bob.jpg'},
|
'bob.com': {uri: '/img/bob.jpg'},
|
||||||
'carla.com': {uri: '/img/carla.jpg'},
|
'carla.com': {uri: '/img/carla.jpg'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BANNER: ImageSourcePropType = {uri: '/img/banner.jpg'}
|
||||||
|
|
|
@ -62,6 +62,10 @@ export const s = StyleSheet.create({
|
||||||
fw200: {fontWeight: '200'},
|
fw200: {fontWeight: '200'},
|
||||||
|
|
||||||
// font sizes
|
// font sizes
|
||||||
|
f9: {fontSize: 9},
|
||||||
|
f10: {fontSize: 10},
|
||||||
|
f11: {fontSize: 11},
|
||||||
|
f12: {fontSize: 12},
|
||||||
f13: {fontSize: 13},
|
f13: {fontSize: 13},
|
||||||
f14: {fontSize: 14},
|
f14: {fontSize: 14},
|
||||||
f15: {fontSize: 15},
|
f15: {fontSize: 15},
|
||||||
|
|
Loading…
Reference in New Issue