Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View user profiles #582

Merged
merged 11 commits into from
Nov 16, 2024
2 changes: 1 addition & 1 deletion example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ REACT_APP_API_URL=https://fallingfruit.org/api/0.3
REACT_APP_API_KEY=AKDJGHSD
REACT_APP_RECAPTCHA_SITE_KEY=6Ld99kUdAAAAAAB5nCofKrQB6Vp-e5wR42u5TNZZ
REACT_APP_GOOGLE_MAPS_API_KEY=
REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID=
REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID=
11 changes: 8 additions & 3 deletions src/components/about/AboutDatasetPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import styled from 'styled-components/macro'

import { getImportById } from '../../utils/api'
import { useAppHistory } from '../../utils/useAppHistory'
import { formatISOString } from '../entry/textFormatters'
import BackButton from '../ui/BackButton'
import { theme } from '../ui/GlobalStyle'
import IconBesideText from '../ui/IconBesideText'
Expand All @@ -21,7 +22,7 @@ const StyledNavBack = styled.div`
`
const AboutDatasetPage = () => {
const { id } = useParams()
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const history = useAppHistory()

const [importData, setImportData] = useState({})
Expand Down Expand Up @@ -62,7 +63,7 @@ const AboutDatasetPage = () => {
</BackButton>
</StyledNavBack>
<h3>
#{id}: {name}
Import #{id}: {name}
</h3>
<a href={url} target="_blank" rel="noreferrer">
{url}
Expand All @@ -78,7 +79,11 @@ const AboutDatasetPage = () => {
</IconBesideText>
<IconBesideText>
<Calendar color={theme.secondaryText} size={20} />
<p>Imported {new Date(created_at).toISOString().slice(0, 10)}</p>
<p>
<time dateTime={created_at}>
{`Imported on ${formatISOString(created_at, i18n.language)}`}
</time>
</p>
</IconBesideText>
{license && (
<IconBesideText>
Expand Down
5 changes: 5 additions & 0 deletions src/components/auth/authRoutes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Route } from 'react-router-dom'

import UserProfile from '../profile/UserProfile'
import AccountPage from './AccountPage'
import ConfirmationPage from './ConfirmationPage'
import ConfirmationResendPage from './ConfirmationResendPage'
Expand Down Expand Up @@ -37,6 +38,10 @@ const pages = [
path: '/users/confirmation',
component: ConfirmationPage,
},
{
path: ['/users/:id'],
component: UserProfile,
},
]

// TODO: Delete export once tabs work off route
Expand Down
17 changes: 14 additions & 3 deletions src/components/entry/EntryOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,20 @@ const EntryOverview = () => {
<User size={20} />
)}
<p>
{locationData.author && locationData.import_id
? t('imported_from', { name: locationData.author })
: t('added_by', { name: locationData.author })}
{locationData.author && locationData.import_id ? (
t('imported_from', { name: locationData.author })
) : (
<>
{t('added_by', { name: '' })}{' '}
{locationData.user_id ? (
<Link to={`/users/${locationData.user_id}`}>
{locationData.author}
</Link>
) : (
locationData.author
)}
</>
)}
{locationData.import_id && (
<>
{locationData.author && ' ('}
Expand Down
18 changes: 17 additions & 1 deletion src/components/entry/Review.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Pencil as PencilIcon } from '@styled-icons/boxicons-solid'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'

import { FRUITING_RATINGS, RATINGS } from '../../constants/ratings'
Expand Down Expand Up @@ -99,6 +100,10 @@ export const StyledImagePreview = styled(ImagePreview)`
margin-right: 7px;
`

const StyledLink = styled(Link)`
color: ${({ theme }) => theme.tertiaryText};
`

const Review = ({
review,
onImageClick,
Expand Down Expand Up @@ -146,7 +151,18 @@ const Review = ({
{!editable && (
<cite>
Reviewed on {formatISOString(review.created_at)}
{review.author && <> by {review.author}</>}
{review.author && (
<>
{' by '}
{review.user_id ? (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to style this - maybe text-decoration: none (undoing the underline styling) and theme.orange for color?
Screenshot from 2024-11-11 10-24-00

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even undoing all the link styling is an option - then the feature won't be discoverable on mobile for now, and will only be known to be a link on desktop because of pointer cursor, but that's fine, since we don't have much content for the pages yet.

<StyledLink to={`/users/${review.user_id}`}>
{review.author}
</StyledLink>
) : (
review.author
)}
</>
)}
{review.observed_on && (
<> (visited {formatISOString(review.observed_on)})</>
)}
Expand Down
82 changes: 82 additions & 0 deletions src/components/profile/UserProfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ArrowBack, Calendar, User } from '@styled-icons/boxicons-regular'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro'

import { getUserById } from '../../utils/api'
import { useAppHistory } from '../../utils/useAppHistory'
import { PageScrollWrapper, PageTemplate } from '../about/PageTemplate'
import { formatISOString } from '../entry/textFormatters'
import BackButton from '../ui/BackButton'
import { theme } from '../ui/GlobalStyle'
import IconBesideText from '../ui/IconBesideText'
import { LoadingOverlay } from '../ui/LoadingIndicator'

const StyledNavBack = styled.div`
svg {
height: 20px;
margin-right: 5px;
}
`
const UserProfile = () => {
const { id } = useParams()
const history = useAppHistory()
const { t, i18n } = useTranslation()
const [userData, setUserData] = useState({})
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
async function fetchUserData() {
setIsLoading(true)

const data = await getUserById(id)
setUserData(data)

setIsLoading(false)
}

fetchUserData()
}, [id])

if (isLoading) {
return <LoadingOverlay />
}

const { created_at, name, bio } = userData

return (
<PageScrollWrapper>
<PageTemplate>
<StyledNavBack>
<BackButton
onClick={(event) => {
event.stopPropagation()
history.goBack()
}}
>
<ArrowBack />
{t('back')}
</BackButton>
</StyledNavBack>
<IconBesideText>
<User size={40} />
<h3>User: {name}</h3>
</IconBesideText>
<p>
<i>{bio}</i>
</p>
<IconBesideText>
<Calendar color={theme.secondaryText} size={20} />
<p>
<time dateTime={created_at}>
{`Joined on ${formatISOString(created_at, i18n.language)}`}
</time>
</p>
</IconBesideText>
</PageTemplate>
</PageScrollWrapper>
)
}

export default UserProfile
5 changes: 5 additions & 0 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ instance.interceptors.request.use((config) => {
'/clusters',
'/imports',
'/imports/:id',
'/users/:id',
]
const isAnonymous =
config.method === 'get' &&
Expand Down Expand Up @@ -184,3 +185,7 @@ export const getImports = () => instance.get(`/imports`)
export const getImportById = (
id: paths['/imports/{id}']['get']['parameters']['path']['id'],
) => instance.get(`/imports/${id}`)

export const getUserById = (
id: paths['/users/{id}']['get']['parameters']['path']['id'],
) => instance.get(`/users/${id}`)
18 changes: 18 additions & 0 deletions src/utils/apiSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ export interface paths {
};
};
};
"/users/{id}": {
get: {
parameters: {
path: {
/** User ID. */
id: components["parameters"]["user_id"];
};
};
responses: {
/** Success */
200: {
content: {
"application/json": components["schemas"]["User"];
};
};
};
};
};
"/locations": {
get: {
parameters: {
Expand Down
Loading