mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-05-13 09:35:30 -04:00
Add message and star button to profile cards in people page
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.compassconnections.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 107
|
||||
versionName "1.24.0"
|
||||
versionCode 108
|
||||
versionName "1.25.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@compass/api",
|
||||
"version": "1.38.2",
|
||||
"version": "1.39.0",
|
||||
"private": true,
|
||||
"description": "Backend API endpoints",
|
||||
"main": "src/serve.ts",
|
||||
|
||||
@@ -4,9 +4,9 @@ import {Profile} from 'common/profiles/profile'
|
||||
import {User} from 'common/user'
|
||||
import {findKey} from 'lodash'
|
||||
import {useRouter} from 'next/router'
|
||||
import {useEffect, useState} from 'react'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {BiEnvelope} from 'react-icons/bi'
|
||||
import {Button} from 'web/components/buttons/button'
|
||||
import {Button, buttonClass} from 'web/components/buttons/button'
|
||||
import {CommentInputTextArea} from 'web/components/comments/comment-input'
|
||||
import {Col} from 'web/components/layout/col'
|
||||
import {Modal, MODAL_CLASS} from 'web/components/layout/modal'
|
||||
@@ -15,26 +15,41 @@ import {useTextEditor} from 'web/components/widgets/editor'
|
||||
import {Tooltip} from 'web/components/widgets/tooltip'
|
||||
import {useFirebaseUser} from 'web/hooks/use-firebase-user'
|
||||
import {useSortedPrivateMessageMemberships} from 'web/hooks/use-private-messages'
|
||||
import {usePrivateUser} from 'web/hooks/use-user'
|
||||
import {usePrivateUser, useUser} from 'web/hooks/use-user'
|
||||
import {api} from 'web/lib/api'
|
||||
import {firebaseLogin} from 'web/lib/firebase/users'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export const SendMessageButton = (props: {
|
||||
toUser: User
|
||||
currentUser: User | undefined | null
|
||||
profile: Profile
|
||||
includeLabel?: boolean
|
||||
circleButton?: boolean
|
||||
text?: string
|
||||
tooltipText?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
accentIfMessaged?: boolean
|
||||
onPointerDown?: () => void
|
||||
size?: string
|
||||
}) => {
|
||||
const {toUser, currentUser, profile, includeLabel, circleButton, text, tooltipText, disabled} =
|
||||
props
|
||||
const {
|
||||
toUser,
|
||||
profile,
|
||||
includeLabel,
|
||||
circleButton,
|
||||
text,
|
||||
tooltipText,
|
||||
disabled,
|
||||
onPointerDown,
|
||||
className,
|
||||
size = 'h-6 w-6',
|
||||
accentIfMessaged,
|
||||
} = props
|
||||
const firebaseUser = useFirebaseUser()
|
||||
const router = useRouter()
|
||||
const privateUser = usePrivateUser()
|
||||
const currentUser = useUser()
|
||||
const channelMemberships = useSortedPrivateMessageMemberships(currentUser?.id)
|
||||
const {memberIdsByChannelId} = channelMemberships
|
||||
const t = useT()
|
||||
@@ -43,17 +58,19 @@ export const SendMessageButton = (props: {
|
||||
const [error, setError] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const previousDirectMessageChannel = findKey(
|
||||
memberIdsByChannelId,
|
||||
(dm) => dm.includes(toUser.id) && dm.length === 1,
|
||||
)
|
||||
|
||||
const previousChannelId =
|
||||
previousDirectMessageChannel !== undefined ? previousDirectMessageChannel : undefined
|
||||
|
||||
const putAccent = previousChannelId !== undefined && accentIfMessaged
|
||||
|
||||
const messageButtonClicked = async () => {
|
||||
if (disabled) return
|
||||
if (!currentUser) return firebaseLogin()
|
||||
const previousDirectMessageChannel = findKey(
|
||||
memberIdsByChannelId,
|
||||
(dm) => dm.includes(toUser.id) && dm.length === 1,
|
||||
)
|
||||
|
||||
const previousChannelId =
|
||||
previousDirectMessageChannel !== undefined ? previousDirectMessageChannel : undefined
|
||||
|
||||
if (previousChannelId) router.push(`/messages/${previousChannelId}`)
|
||||
else setOpenComposeModal(true)
|
||||
}
|
||||
@@ -138,12 +155,23 @@ export const SendMessageButton = (props: {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip text={tooltipText || t('send_message.button_label', 'Message')} noTap>
|
||||
<Tooltip
|
||||
text={
|
||||
tooltipText ||
|
||||
(putAccent ? t('send_message.', 'Follow Up') : t('send_message.button_label', 'Message'))
|
||||
}
|
||||
noTap
|
||||
>
|
||||
{text ? (
|
||||
<Button
|
||||
className={clsx('h-fit gap-1', disabled && 'opacity-50 cursor-not-allowed')}
|
||||
color={'primary'}
|
||||
onClick={messageButtonClicked}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
onPointerDown?.()
|
||||
messageButtonClicked()
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
disabled={disabled}
|
||||
>
|
||||
{text}
|
||||
@@ -154,7 +182,12 @@ export const SendMessageButton = (props: {
|
||||
'h-7 w-7 rounded-full transition-colors',
|
||||
disabled ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary-900 hover:bg-primary-600',
|
||||
)}
|
||||
onClick={messageButtonClicked}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
onPointerDown?.()
|
||||
messageButtonClicked()
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
disabled={disabled}
|
||||
>
|
||||
<BiEnvelope
|
||||
@@ -163,14 +196,27 @@ export const SendMessageButton = (props: {
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={messageButtonClicked}
|
||||
onPointerDown={onPointerDown}
|
||||
disabled={disabled}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
onPointerDown?.()
|
||||
messageButtonClicked()
|
||||
}}
|
||||
className={clsx(
|
||||
'border-canvas-300 flex items-center gap-1.5 rounded-lg border px-2 py-2 text-sm text-primary-700 transition-colors hover:border-primary-400 hover:bg-primary-50',
|
||||
'relative border border-canvas-200',
|
||||
buttonClass('xs', 'none'),
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
className,
|
||||
putAccent
|
||||
? 'bg-green-200 border-green-500 !text-green-500'
|
||||
: 'bg-canvas-50 border-canvas-300 text-ink-500 hover:border-primary-400 hover:bg-primary-50',
|
||||
)}
|
||||
>
|
||||
<BiEnvelope className={clsx('h-5 w-5', includeLabel && 'mr-2')} />{' '}
|
||||
{putAccent && (
|
||||
<span className="absolute top-[3px] right-[3px] w-[7px] h-[7px] rounded-full bg-green-500 border-[1.5px] border-canvas-50" />
|
||||
)}
|
||||
<BiEnvelope className={clsx(size, includeLabel && 'mr-2')} />{' '}
|
||||
{includeLabel && <>{t('send_message.button_label', 'Message')}</>}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -23,12 +23,14 @@ import {PiMagnifyingGlassBold} from 'react-icons/pi'
|
||||
import GenderIcon from 'web/components/gender-icon'
|
||||
import {IconWithInfo} from 'web/components/icons'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {SendMessageButton} from 'web/components/messaging/send-message-button'
|
||||
import {ProfileLocation} from 'web/components/profile/profile-location'
|
||||
import {getSeekingText} from 'web/components/profile-about'
|
||||
import {CompatibleBadge} from 'web/components/widgets/compatible-badge'
|
||||
import {Content} from 'web/components/widgets/editor'
|
||||
import HideProfileButton from 'web/components/widgets/hide-profile-button'
|
||||
import {CompassLoadingIndicator} from 'web/components/widgets/loading-indicator'
|
||||
import {StarButton} from 'web/components/widgets/star-button'
|
||||
import {LoadMoreUntilNotVisible} from 'web/components/widgets/visibility-observer'
|
||||
import {useChoicesContext} from 'web/hooks/use-choices'
|
||||
import {isDark, useTheme} from 'web/hooks/use-theme'
|
||||
@@ -144,7 +146,16 @@ function ProfilePreview(props: {
|
||||
onUndoHidden?: (userId: string) => void
|
||||
displayOptions?: Partial<DisplayOptions>
|
||||
}) {
|
||||
const {profile, compatibilityScore, onHide, isHidden, onUndoHidden, displayOptions} = props
|
||||
const {
|
||||
profile,
|
||||
compatibilityScore,
|
||||
onHide,
|
||||
isHidden,
|
||||
onUndoHidden,
|
||||
displayOptions,
|
||||
hasStar,
|
||||
refreshStars,
|
||||
} = props
|
||||
|
||||
const {
|
||||
showPhotos,
|
||||
@@ -168,7 +179,6 @@ function ProfilePreview(props: {
|
||||
const {user} = profile
|
||||
const choicesIdsToLabels = useChoicesContext()
|
||||
const t = useT()
|
||||
// const currentUser = useUser()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [showRing, setShowRing] = useState(false)
|
||||
@@ -346,18 +356,21 @@ function ProfilePreview(props: {
|
||||
<Col className={clsx('relative w-full rounded-xl transition-all text-sm')}>
|
||||
<Row
|
||||
className={clsx(
|
||||
'absolute top-2 right-2 items-start justify-end px-2 pb-3 z-10',
|
||||
'absolute top-2 right-2 items-start justify-end px-2 pb-3 z-10 gap-1',
|
||||
isPhotoRendered && (cardSize === 'large' ? 'mr-0 sm:mr-60' : 'mr-40'),
|
||||
)}
|
||||
>
|
||||
{compatibilityScore && (
|
||||
<CompatibleBadge compatibility={compatibilityScore} className={'pt-1'} />
|
||||
<CompatibleBadge
|
||||
compatibility={compatibilityScore}
|
||||
className={clsx('pt-1 text-xs', cardSize !== 'large' && 'hidden sm:flex')}
|
||||
/>
|
||||
)}
|
||||
{onHide && (
|
||||
<HideProfileButton
|
||||
hiddenUserId={profile.user_id}
|
||||
onHidden={onHide}
|
||||
className="ml-2"
|
||||
className="ml-1"
|
||||
stopPropagation
|
||||
eyeOff
|
||||
onPointerDown={() => {
|
||||
@@ -365,6 +378,32 @@ function ProfilePreview(props: {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{hasStar !== undefined && (
|
||||
<StarButton
|
||||
targetProfile={profile}
|
||||
isStarred={hasStar}
|
||||
refresh={refreshStars}
|
||||
size={'h-4 w-4'}
|
||||
className="h-7 w-7 !rounded-lg !p-1 hover:border-primary-400 hover:bg-primary-50"
|
||||
onPointerDown={() => {
|
||||
hideButtonClickedRef.current = true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{user && (
|
||||
<div className={clsx(cardSize !== 'large' && 'hidden sm:flex')}>
|
||||
<SendMessageButton
|
||||
toUser={user}
|
||||
profile={profile}
|
||||
size={'h-4 w-4'}
|
||||
className={clsx('!p-1 w-7 h-7')}
|
||||
accentIfMessaged
|
||||
onPointerDown={() => {
|
||||
hideButtonClickedRef.current = true
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<div className={clsx('flex lg:flex-row h-full lg:justify-between', cardClass)}>
|
||||
|
||||
@@ -89,7 +89,6 @@ export function ConnectActions(props: {profile: Profile; user: User}) {
|
||||
{profile.allow_direct_messaging || matches.length > 0 ? (
|
||||
<SendMessageButton
|
||||
toUser={user}
|
||||
currentUser={currentUser}
|
||||
profile={profile}
|
||||
text={t('messaging.send_thoughtful_message', 'Send them a thoughtful message')}
|
||||
/>
|
||||
|
||||
@@ -328,7 +328,6 @@ export function ProfileHeaderActions(props: {
|
||||
{currentUser && showMessageButton && (
|
||||
<SendMessageButton
|
||||
toUser={user}
|
||||
currentUser={currentUser}
|
||||
profile={profile}
|
||||
tooltipText={tooltipText}
|
||||
disabled={!profile.allow_direct_messaging}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function HideProfileButton(props: HideProfileButtonProps) {
|
||||
>
|
||||
<button
|
||||
className={clsx(
|
||||
'border border-canvas-200 rounded-md p-1 hover:bg-canvas-200 focus:outline-none',
|
||||
'relative inline-flex items-center justify-center border bg-canvas-50 border-canvas-300 text-ink-300 hover:border-primary-400 hover:bg-primary-50 rounded-lg h-7 w-7',
|
||||
className,
|
||||
)}
|
||||
disabled={submitting}
|
||||
@@ -91,9 +91,9 @@ export function HideProfileButton(props: HideProfileButtonProps) {
|
||||
}
|
||||
>
|
||||
{hidden || eyeOff ? (
|
||||
<EyeSlashIcon className={clsx('h-4 w-4 guidance', iconClassName)} />
|
||||
<EyeSlashIcon className={clsx('h-4 w-4 text-ink-500', iconClassName)} />
|
||||
) : (
|
||||
<EyeIcon className={clsx('h-4 w-4 guidance', iconClassName)} />
|
||||
<EyeIcon className={clsx('h-4 w-4 text-ink-500', iconClassName)} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -14,8 +14,10 @@ export const StarButton = (props: {
|
||||
refresh: () => Promise<void>
|
||||
hideTooltip?: boolean
|
||||
className?: string
|
||||
size?: string
|
||||
onPointerDown?: () => void
|
||||
}) => {
|
||||
const {targetProfile, refresh, hideTooltip, className} = props
|
||||
const {targetProfile, refresh, hideTooltip, className, size = 'w-6 h-6', onPointerDown} = props
|
||||
const targetId = targetProfile.user_id
|
||||
const [isStarred, setIsStarred] = useState(props.isStarred)
|
||||
const t = useT()
|
||||
@@ -41,18 +43,21 @@ export const StarButton = (props: {
|
||||
|
||||
const button = (
|
||||
<button
|
||||
className={clsx(buttonClass('xs', 'none'), 'text-ink-500 group !rounded-full', className)}
|
||||
className={clsx(
|
||||
'border border-canvas-200',
|
||||
buttonClass('xs', 'none'),
|
||||
isStarred
|
||||
? 'bg-primary-50 border-primary-200 text-primary-600'
|
||||
: 'bg-canvas-50 border-canvas-300 text-ink-500 hover:border-primary-400 hover:bg-primary-50',
|
||||
className,
|
||||
)}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
star()
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<StarIcon
|
||||
className={clsx(
|
||||
'h-7 w-7 transition-colors group-hover:fill-yellow-400/70',
|
||||
isStarred && 'fill-yellow-400 stroke-yellow-500 dark:stroke-yellow-600',
|
||||
)}
|
||||
/>
|
||||
<StarIcon className={clsx(size, isStarred && 'fill-primary-500')} />
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export function Tooltip(props: {
|
||||
as="div"
|
||||
ref={refs.setFloating as any}
|
||||
style={{position: strategy, top: y ?? 0, left: x ?? 0}}
|
||||
className="text-ink-1000 bg-canvas-50 z-20 w-max max-w-xs whitespace-normal rounded-lg px-2 py-1 text-center text-sm font-medium border border-canvas-100 shadow shadow-canvas-100"
|
||||
className="text-ink-1000 bg-primary-100 z-20 w-max max-w-xs whitespace-normal rounded-lg px-2 py-1 text-center text-sm font-medium border border-primary-300 shadow shadow-canvas-100"
|
||||
suppressHydrationWarning={suppressHydrationWarning}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
--color-ink-600: 120 108 92;
|
||||
|
||||
/* Green - Accents */
|
||||
--color-green-200: #f0faf3;
|
||||
--color-green-500: 107 143 113; /* Accent - Sage Green (#6B8F71) */
|
||||
|
||||
/* Canvas - Backgrounds & Surfaces */
|
||||
@@ -182,7 +183,7 @@
|
||||
/*--color-green-500: 34 197 94; !* standard green *!*/
|
||||
--color-green-400: 74 222 128;
|
||||
--color-green-300: 110 231 183; /* vibrant but not neon */
|
||||
--color-green-200: 167 243 208; /* gentle mid-light tone */
|
||||
/*--color-green-200: 167 243 208; !* gentle mid-light tone *!*/
|
||||
--color-green-100: 209 260 229; /* soft and airy */
|
||||
--color-green-50: 240 263 245; /* subtle, barely tinted background */
|
||||
|
||||
@@ -222,8 +223,11 @@
|
||||
--color-ink-500: 176 160 140; /* Muted Text - shifted to warm tan */
|
||||
--color-ink-600: 156 140 120;
|
||||
|
||||
/* Green - Sage looks great on dark brown */
|
||||
--color-green-500: 125 160 131; /* Lightened Sage */
|
||||
/* Button background — barely-there dark green surface */
|
||||
--color-green-200: 32 54 38;
|
||||
|
||||
/* Icon, dot, text — readable sage on that dark surface */
|
||||
--color-green-500: 125 160 131;
|
||||
|
||||
/* High Contrast */
|
||||
--color-ink-1000: 255 255 255; /* white */
|
||||
@@ -298,7 +302,7 @@
|
||||
/*--color-green-500: 34 197 94; !* standard green *!*/
|
||||
--color-green-400: 22 163 74;
|
||||
--color-green-300: 21 128 61;
|
||||
--color-green-200: 22 101 52;
|
||||
/*--color-green-200: 22 101 52;*/
|
||||
--color-green-100: 20 83 45;
|
||||
--color-green-50: 5 46 22; /* darkest green */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user