-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: redesigned ObsDetails screen in debug mode (#2580)
* Create ObsDetailsDefaultMode and rearrange items on top of screen * Move activity, details, and more into three different sections instead of tabs * Styling cleanup; change Activity name to Community * Fix scroll to activity item from Notifications * Add ObsDetailsDefaultMode unit tests * Show kebab menu on other users' observations
- Loading branch information
1 parent
ce7e91e
commit 1d340eb
Showing
67 changed files
with
6,510 additions
and
199 deletions.
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
src/components/ObsDetailsDefaultMode/CommunitySection/ActivityHeader.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
// @flow | ||
import classnames from "classnames"; | ||
import ActivityHeaderKebabMenu from "components/ObsDetails/ActivityTab/ActivityHeaderKebabMenu"; | ||
import WithdrawIDSheet from "components/ObsDetails/Sheets/WithdrawIDSheet"; | ||
import { | ||
ActivityIndicator, | ||
Body4, DateDisplay, | ||
INatIcon, InlineUser, TextInputSheet, WarningSheet | ||
} from "components/SharedComponents"; | ||
import { | ||
View | ||
} from "components/styledComponents"; | ||
import { t } from "i18next"; | ||
import type { Node } from "react"; | ||
import React, { useCallback, useState } from "react"; | ||
import colors from "styles/tailwindColors"; | ||
|
||
type Props = { | ||
classNameMargin?: string, | ||
currentUser: boolean, | ||
deleteComment: Function, | ||
flagged: boolean, | ||
idWithdrawn?: boolean, | ||
isConnected?: boolean, | ||
item: Object, | ||
loading: boolean, | ||
updateCommentBody: Function, | ||
updateIdentification: Function, | ||
geoprivacy: string, | ||
taxonGeoprivacy: string, | ||
belongsToCurrentUser: boolean | ||
} | ||
|
||
const ActivityHeader = ( { | ||
classNameMargin, | ||
currentUser, | ||
deleteComment, | ||
flagged, | ||
idWithdrawn, | ||
isConnected, | ||
item, | ||
loading, | ||
updateCommentBody, | ||
updateIdentification, | ||
geoprivacy, | ||
taxonGeoprivacy, | ||
belongsToCurrentUser | ||
}:Props ): Node => { | ||
const [showEditCommentSheet, setShowEditCommentSheet] = useState( false ); | ||
const [showDeleteCommentSheet, setShowDeleteCommentSheet] = useState( false ); | ||
const [showWithdrawIDSheet, setShowWithdrawIDSheet] = useState( false ); | ||
const { category, user, vision } = item; | ||
|
||
const itemType = item.category | ||
? "Identification" | ||
: "Comment"; | ||
|
||
const renderIcon = useCallback( () => { | ||
if ( idWithdrawn ) { | ||
return ( | ||
<View className="opacity-50"> | ||
<INatIcon name="ban" color={colors.primary} size={22} /> | ||
</View> | ||
); | ||
} | ||
|
||
if ( vision ) return <INatIcon name="sparkly-label" size={22} />; | ||
if ( flagged ) return <INatIcon name="flag" color={colors.warningYellow} size={22} />; | ||
return null; | ||
}, [ | ||
flagged, | ||
idWithdrawn, | ||
vision | ||
] ); | ||
|
||
const renderStatus = useCallback( () => { | ||
if ( flagged ) { | ||
return ( | ||
<Body4 maxFontSizeMultiplier={1}> | ||
{t( "Flagged" )} | ||
</Body4> | ||
); | ||
} | ||
if ( idWithdrawn ) { | ||
return ( | ||
<Body4 maxFontSizeMultiplier={1}> | ||
{ t( "ID-Withdrawn" )} | ||
</Body4> | ||
); | ||
} | ||
if ( category ) { | ||
let categoryText; | ||
switch ( category ) { | ||
case "improving": | ||
categoryText = t( "improving--identification" ); | ||
break; | ||
case "maverick": | ||
categoryText = t( "maverick--identification" ); | ||
break; | ||
case "leading": | ||
categoryText = t( "leading--identification" ); | ||
break; | ||
default: | ||
categoryText = t( "supporting--identification" ); | ||
} | ||
return ( | ||
<Body4 maxFontSizeMultiplier={1}> | ||
{ categoryText } | ||
</Body4> | ||
); | ||
} | ||
return ( | ||
<Body4 /> | ||
); | ||
}, [ | ||
category, | ||
flagged, | ||
idWithdrawn | ||
] ); | ||
|
||
return ( | ||
<View className={classnames( "flex-row justify-between", classNameMargin )}> | ||
<InlineUser user={user} isConnected={isConnected} /> | ||
<View className="flex-row items-center space-x-[15px] -mr-[15px]"> | ||
{renderIcon()} | ||
{renderStatus()} | ||
{/* | ||
Note that even though the date is conditionally rendered, we need to | ||
wrap it in a View that's always there so the space-x-[] can be | ||
calculated | ||
*/} | ||
<View> | ||
{item.created_at && ( | ||
<DateDisplay | ||
dateString={item.updated_at || item.created_at} | ||
geoprivacy={geoprivacy} | ||
taxonGeoprivacy={taxonGeoprivacy} | ||
belongsToCurrentUser={belongsToCurrentUser} | ||
maxFontSizeMultiplier={1} | ||
/> | ||
)} | ||
</View> | ||
{ | ||
loading | ||
? ( | ||
<View className="mr-[20px]"> | ||
<ActivityIndicator size={10} /> | ||
</View> | ||
) | ||
: ( | ||
<ActivityHeaderKebabMenu | ||
currentUser={currentUser} | ||
itemType={itemType} | ||
current={item.current} | ||
setShowWithdrawIDSheet={setShowWithdrawIDSheet} | ||
updateIdentification={updateIdentification} | ||
setShowEditCommentSheet={setShowEditCommentSheet} | ||
setShowDeleteCommentSheet={setShowDeleteCommentSheet} | ||
/> | ||
) | ||
} | ||
{( currentUser && showWithdrawIDSheet ) && ( | ||
<WithdrawIDSheet | ||
onPressClose={() => setShowWithdrawIDSheet( false )} | ||
taxon={item.taxon} | ||
updateIdentification={updateIdentification} | ||
/> | ||
)} | ||
|
||
{( currentUser && showEditCommentSheet ) && ( | ||
<TextInputSheet | ||
onPressClose={() => setShowEditCommentSheet( false )} | ||
headerText={t( "EDIT-COMMENT" )} | ||
initialInput={item.body} | ||
confirm={textInput => updateCommentBody( textInput )} | ||
/> | ||
)} | ||
{( currentUser && showDeleteCommentSheet ) && ( | ||
<WarningSheet | ||
onPressClose={( ) => setShowDeleteCommentSheet( false )} | ||
headerText={t( "DELETE-COMMENT--question" )} | ||
confirm={deleteComment} | ||
buttonText={t( "DELETE" )} | ||
handleSecondButtonPress={( ) => setShowDeleteCommentSheet( false )} | ||
secondButtonText={t( "CANCEL" )} | ||
/> | ||
)} | ||
</View> | ||
</View> | ||
); | ||
}; | ||
|
||
export default ActivityHeader; |
136 changes: 136 additions & 0 deletions
136
src/components/ObsDetailsDefaultMode/CommunitySection/ActivityHeaderContainer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// @flow | ||
|
||
import { deleteComments, updateComment } from "api/comments"; | ||
import { updateIdentification as apiUpdateIdentification } from "api/identifications"; | ||
import { isCurrentUser } from "components/LoginSignUp/AuthenticationService.ts"; | ||
import type { Node } from "react"; | ||
import React, { useEffect, useState } from "react"; | ||
import useAuthenticatedMutation from "sharedHooks/useAuthenticatedMutation"; | ||
|
||
import ActivityHeader from "./ActivityHeader"; | ||
|
||
type Props = { | ||
classNameMargin?: string, | ||
idWithdrawn?: boolean, | ||
isConnected?: boolean, | ||
item: Object, | ||
refetchRemoteObservation?: Function, | ||
geoprivacy: string, | ||
taxonGeoprivacy: string, | ||
belongsToCurrentUser: boolean | ||
} | ||
|
||
const ActivityHeaderContainer = ( { | ||
classNameMargin, | ||
idWithdrawn, | ||
isConnected, | ||
item, | ||
refetchRemoteObservation, | ||
geoprivacy, | ||
taxonGeoprivacy, | ||
belongsToCurrentUser | ||
}:Props ): Node => { | ||
const [currentUser, setCurrentUser] = useState( false ); | ||
const [loading, setLoading] = useState( false ); | ||
const { user } = item; | ||
|
||
const flagged = item.flags?.some( flag => !flag.resolved ); | ||
|
||
useEffect( ( ) => { | ||
const isActiveUserTheCurrentUser = async ( ) => { | ||
const current = await isCurrentUser( user?.login ); | ||
setCurrentUser( current ); | ||
}; | ||
isActiveUserTheCurrentUser( ); | ||
}, [user] ); | ||
|
||
const deleteCommentMutation = useAuthenticatedMutation( | ||
( uuid, optsWithAuth ) => deleteComments( uuid, optsWithAuth ), | ||
{ | ||
onSuccess: ( ) => { | ||
setLoading( false ); | ||
if ( refetchRemoteObservation ) { | ||
refetchRemoteObservation( ); | ||
} | ||
}, | ||
onError: () => { | ||
setLoading( false ); | ||
} | ||
} | ||
); | ||
|
||
const deleteUserComment = () => { | ||
setLoading( true ); | ||
deleteCommentMutation.mutate( item.uuid ); | ||
}; | ||
|
||
const updateCommentMutation = useAuthenticatedMutation( | ||
( uuid, optsWithAuth ) => updateComment( uuid, optsWithAuth ), | ||
{ | ||
onSuccess: () => { | ||
setLoading( false ); | ||
if ( refetchRemoteObservation ) { | ||
refetchRemoteObservation( ); | ||
} | ||
}, | ||
onError: () => { | ||
setLoading( false ); | ||
} | ||
} | ||
); | ||
|
||
const updateCommentBody = comment => { | ||
const updateCommentParams = { | ||
id: item.uuid, | ||
comment: { | ||
body: comment | ||
} | ||
}; | ||
setLoading( true ); | ||
updateCommentMutation.mutate( updateCommentParams ); | ||
}; | ||
|
||
const updateIdentificationMutation = useAuthenticatedMutation( | ||
( uuid, optsWithAuth ) => apiUpdateIdentification( uuid, optsWithAuth ), | ||
{ | ||
onSuccess: () => { | ||
setLoading( false ); | ||
if ( refetchRemoteObservation ) { | ||
refetchRemoteObservation( ); | ||
} | ||
}, | ||
onError: () => { | ||
setLoading( false ); | ||
} | ||
} | ||
); | ||
|
||
const updateIdentification = identification => { | ||
const updateIdentificationParams = { | ||
id: item.uuid, | ||
identification | ||
}; | ||
setLoading( true ); | ||
updateIdentificationMutation.mutate( updateIdentificationParams ); | ||
}; | ||
|
||
return ( | ||
<ActivityHeader | ||
loading={loading} | ||
item={item} | ||
currentUser={currentUser} | ||
idWithdrawn={idWithdrawn} | ||
classNameMargin={classNameMargin} | ||
flagged={flagged} | ||
updateCommentBody={updateCommentBody} | ||
deleteComment={deleteUserComment} | ||
updateIdentification={updateIdentification} | ||
isConnected={isConnected} | ||
geoprivacy={geoprivacy} | ||
taxonGeoprivacy={taxonGeoprivacy} | ||
belongsToCurrentUser={belongsToCurrentUser} | ||
/> | ||
); | ||
}; | ||
|
||
export default ActivityHeaderContainer; |
Oops, something went wrong.