Add option to view pics on profile cards

This commit is contained in:
MartinBraquet
2026-03-08 17:38:43 +01:00
parent 74f948e6ca
commit 6c54a9adf0
9 changed files with 117 additions and 22 deletions

View File

@@ -362,6 +362,7 @@
"filter.last_active.3months": "Letzte 3 Monate",
"filter.reset": "Zurücksetzen",
"filter.short_bio_toggle": "Kurze Bios einschließen",
"filter.show_photos": "Profilfotos anzeigen",
"filter.wants_kids.any_preference": "Alle Präferenzen",
"filter.wants_kids.doesnt_want_kids": "Möchte keine Kinder",
"filter.wants_kids.either": "Egal",
@@ -1161,6 +1162,7 @@
"stats.total": "Gesamt",
"stats.votes": "Stimmen",
"stats.with_bio": "Mit Bio",
"stats.gender_ratio": "Geschlechterverteilung",
"sticky_format_menu.add_embed": "Embed hinzufügen",
"sticky_format_menu.add_emoji": "Emoji hinzufügen",
"sticky_format_menu.upload_image": "Bild hochladen",

View File

@@ -362,6 +362,7 @@
"filter.last_active.3months": "3 derniers mois",
"filter.reset": "Réinitialiser",
"filter.short_bio_toggle": "Inclure les profils incomplets",
"filter.show_photos": "Afficher les photos",
"filter.wants_kids.any_preference": "N'importe",
"filter.wants_kids.doesnt_want_kids": "Ne veut pas d'enfants",
"filter.wants_kids.either": "N'importe",
@@ -1160,6 +1161,7 @@
"stats.total": "Total",
"stats.votes": "Votes",
"stats.with_bio": "Complétés",
"stats.gender_ratio": "Répartition Hommes / Femmes",
"sticky_format_menu.add_embed": "Ajouter un embed",
"sticky_format_menu.add_emoji": "Ajouter un emoji",
"sticky_format_menu.upload_image": "Ajouter une image",

View File

@@ -0,0 +1,7 @@
export type RenderingOptions = {
showPhotos: boolean | null | undefined
}
export const initialRenderingOptions: RenderingOptions = {
showPhotos: false,
}

View File

@@ -6,6 +6,7 @@ import {formatFilters, SKIPPED_FORMAT_FILTERS_KEYS} from 'common/filters-format'
import {Gender} from 'common/gender'
import {OptionTableKey} from 'common/profiles/constants'
import {Profile} from 'common/profiles/profile'
import {RenderingOptions} from 'common/profiles-rendering'
import {nullifyDictValues, removeNullOrUndefinedProps, sampleDictByPrefix} from 'common/util/object'
import {ReactNode, useState} from 'react'
import {
@@ -27,6 +28,7 @@ import {
import {ReligionFilter, ReligionFilterText} from 'web/components/filters/religion-filter'
import {RomanticFilter, RomanticFilterText} from 'web/components/filters/romantic-filter'
import {ShortBioToggle} from 'web/components/filters/short-bio-toggle'
import {ShowPhotosToggle} from 'web/components/filters/show-photos-toggle'
import {KidsLabel, WantsKidsFilter} from 'web/components/filters/wants-kids-filter'
import {FilterGuide} from 'web/components/guidance'
import {Col} from 'web/components/layout/col'
@@ -186,8 +188,8 @@ function SelectedFiltersSummary(props: {
}
function Filters(props: {
filters: Partial<FilterFields>
youProfile: Profile | undefined | null
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
clearFilters: () => void
setYourFilters: (checked: boolean) => void
@@ -196,6 +198,8 @@ function Filters(props: {
raisedInLocationFilterProps: LocationFilterProps
includeRelationshipFilters: boolean | undefined
choices: Record<OptionTableKey, Record<string, string>>
renderingOptions: Partial<RenderingOptions>
updateRenderingOptions: (newState: Partial<RenderingOptions>) => void
}) {
const t = useT()
const {
@@ -208,6 +212,8 @@ function Filters(props: {
locationFilterProps,
raisedInLocationFilterProps,
includeRelationshipFilters,
renderingOptions,
updateRenderingOptions,
choices,
} = props
@@ -247,6 +253,14 @@ function Filters(props: {
<ShortBioToggle updateFilter={updateFilter} filters={filters} hidden={false} />
</Col>
{/* Show Photos */}
<Col className="p-4 pt-0">
<ShowPhotosToggle
updateRenderingOptions={updateRenderingOptions}
renderingOptions={renderingOptions}
/>
</Col>
{/* ALWAYS VISIBLE FILTERS */}
{/* CONNECTION - Always visible */}
@@ -807,6 +821,8 @@ export function FiltersElement(props: {
isYourFilters: boolean
locationFilterProps: LocationFilterProps
raisedInLocationFilterProps: LocationFilterProps
renderingOptions: Partial<RenderingOptions>
updateRenderingOptions: (newState: Partial<RenderingOptions>) => void
}) {
const {
filters,
@@ -817,6 +833,8 @@ export function FiltersElement(props: {
isYourFilters,
locationFilterProps,
raisedInLocationFilterProps,
renderingOptions,
updateRenderingOptions,
} = props
const youSeekingRelationship = youProfile?.pref_relation_styles?.includes('relationship')
const {choices: interestChoices} = useChoices('interests')
@@ -839,6 +857,8 @@ export function FiltersElement(props: {
raisedInLocationFilterProps={raisedInLocationFilterProps}
includeRelationshipFilters={youSeekingRelationship}
choices={choices}
renderingOptions={renderingOptions}
updateRenderingOptions={updateRenderingOptions}
/>
)
}

View File

@@ -0,0 +1,33 @@
import clsx from 'clsx'
import {RenderingOptions} from 'common/profiles-rendering'
import {Row} from 'web/components/layout/row'
import {useT} from 'web/lib/locale'
export function ShowPhotosToggle(props: {
renderingOptions: Partial<RenderingOptions>
updateRenderingOptions: (newState: Partial<RenderingOptions>) => void
}) {
const {renderingOptions, updateRenderingOptions} = props
const t = useT()
const label = t('filter.show_photos', 'Show profile pictures')
const on = renderingOptions.showPhotos ?? true
return (
<Row className={clsx('mr-2 items-center hover-bold', on && 'font-semibold')}>
<input
id={label}
type="checkbox"
className="border-ink-300 bg-canvas-0 dark:border-ink-500 text-primary-600 focus:ring-primary-500 h-4 w-4 rounded hover:bg-canvas-200 cursor-pointer"
checked={on}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateRenderingOptions({showPhotos: e.target.checked})
}
/>
<label htmlFor={label} className={clsx('text-ink-600 ml-2')}>
{label}
</label>
</Row>
)
}

View File

@@ -23,10 +23,10 @@ export const useFilters = (you: Profile | undefined, fromSignup?: boolean) => {
: {...initialFilters, orderBy: 'created_time' as const}
const getInitialFilters = (): Partial<FilterFields> => {
if (fromSignup) {
return {...baseFilters, languages: [LOCALE_TO_LANGUAGE[getLocale()]]}
return {
...baseFilters,
languages: fromSignup ? [LOCALE_TO_LANGUAGE[getLocale()]] : undefined,
}
return baseFilters
}
const [filters, setFilters] = usePersistentLocalState<Partial<FilterFields>>(

View File

@@ -3,6 +3,7 @@ import clsx from 'clsx'
import {CompatibilityScore} from 'common/profiles/compatibility-score'
import {Profile} from 'common/profiles/profile'
import {capitalize} from 'lodash'
import Image from 'next/image'
import Link from 'next/link'
import {Row} from 'web/components/layout/row'
import {CompatibleBadge} from 'web/components/widgets/compatible-badge'
@@ -28,6 +29,7 @@ export const ProfileGrid = (props: {
onHide?: (userId: string) => void
hiddenUserIds?: string[]
onUndoHidden?: (userId: string) => void
showPhotos?: boolean | null
}) => {
const {
profiles,
@@ -40,6 +42,7 @@ export const ProfileGrid = (props: {
onHide,
hiddenUserIds,
onUndoHidden,
showPhotos,
} = props
const user = useUser()
@@ -65,6 +68,7 @@ export const ProfileGrid = (props: {
onHide={onHide}
isHidden={hiddenUserIds?.includes(profile.user_id) ?? false}
onUndoHidden={onUndoHidden}
showPhotos={showPhotos}
/>
))}
</div>
@@ -100,8 +104,9 @@ function ProfilePreview(props: {
onHide?: (userId: string) => void
isHidden?: boolean
onUndoHidden?: (userId: string) => void
showPhotos?: boolean | null
}) {
const {profile, compatibilityScore, onHide, isHidden, onUndoHidden} = props
const {profile, compatibilityScore, onHide, isHidden, onUndoHidden, showPhotos} = props
const {user} = profile
const choicesIdsToLabels = useAllChoices()
const t = useT()
@@ -165,6 +170,7 @@ function ProfilePreview(props: {
// return null
// }
const isPhotoRendered = showPhotos !== false && profile.pinned_url
return (
<Link
// onClick={() => track('click profile preview')}
@@ -172,22 +178,6 @@ function ProfilePreview(props: {
className="cursor-pointer group block rounded-lg overflow-hidden bg-transparent hover:bg-gray-50 dark:hover:bg-gray-800/50 h-full border border-canvas-300"
>
<Col className="relative h-40 w-full overflow-hidden rounded transition-all">
{/*{pinned_url ? (*/}
{/* <Image*/}
{/* src={pinned_url}*/}
{/* width={180}*/}
{/* height={240}*/}
{/* alt=""*/}
{/* className="h-full w-full object-cover"*/}
{/* loading="lazy"*/}
{/* priority={false}*/}
{/* />*/}
{/*) : (*/}
{/* <Col className="bg-ink-300 h-full w-full items-center justify-center">*/}
{/* <UserIcon className="h-20 w-20" />*/}
{/* </Col>*/}
{/*)}*/}
<Row className="absolute top-2 right-2 items-start justify-end px-2 pb-3 z-10">
{/* {currentUser ? (*/}
{/* <StarButton*/}
@@ -215,7 +205,12 @@ function ProfilePreview(props: {
)}
</Row>
<Col className="absolute inset-x-0 top-[-15px] bg-gradient-to-b to-transparent px-4 pt-0">
<Col
className={clsx(
'absolute inset-x-0 top-[-15px] bg-gradient-to-b to-transparent px-4 pt-0',
isPhotoRendered && 'mr-24',
)}
>
<div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-medium text-gray-900 dark:text-white truncate">
@@ -252,6 +247,21 @@ function ProfilePreview(props: {
{/* {city} • {capitalize(convertGender(gender as Gender))}*/}
{/*</Row>*/}
</Col>
{/* Profile image moved to bottom right */}
{isPhotoRendered && (
<div className="absolute bottom-4 right-2 w-24 h-24 rounded-xl overflow-hidden shadow-lg">
<Image
src={profile.pinned_url!}
width={128}
height={128}
alt=""
className="h-full w-full object-cover"
loading="lazy"
priority={false}
/>
</div>
)}
</Col>
</Link>
)

View File

@@ -23,6 +23,7 @@ import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-sta
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
import {useProfile} from 'web/hooks/use-profile'
import {useCompatibleProfiles} from 'web/hooks/use-profiles'
import {useRenderingOptions} from 'web/hooks/use-rendering-options'
import {useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {useLocale, useT} from 'web/lib/locale'
@@ -45,6 +46,8 @@ export function ProfilesHome() {
raisedInLocationFilterProps,
} = useFilters(you ?? undefined, fromSignup)
const {renderingOptions, updateRenderingOptions} = useRenderingOptions()
const [profiles, setProfiles] = usePersistentInMemoryState<Profile[] | undefined>(
undefined,
'profiles',
@@ -196,6 +199,8 @@ export function ProfilesHome() {
isYourFilters={isYourFilters}
locationFilterProps={locationFilterProps}
raisedInLocationFilterProps={raisedInLocationFilterProps}
renderingOptions={renderingOptions}
updateRenderingOptions={updateRenderingOptions}
/>
)
@@ -335,6 +340,7 @@ export function ProfilesHome() {
onHide={onHide}
hiddenUserIds={recentlyHiddenIds}
onUndoHidden={onUndoHidden}
showPhotos={renderingOptions.showPhotos}
/>
</>
)}

View File

@@ -0,0 +1,15 @@
import {initialRenderingOptions, RenderingOptions} from 'common/profiles-rendering'
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
export function useRenderingOptions() {
const [renderingOptions, setRenderingOptions] = usePersistentLocalState<RenderingOptions>(
initialRenderingOptions,
'rendering-options',
)
const updateRenderingOptions = (newState: Partial<RenderingOptions>) => {
const updatedState = {...newState}
setRenderingOptions((prevState) => ({...prevState, ...updatedState}))
}
return {renderingOptions, updateRenderingOptions}
}