diff --git a/android/app/build.gradle b/android/app/build.gradle index 2fea6f33..94d39166 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.compassconnections.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 101 - versionName "1.22.0" + versionCode 102 + versionName "1.23.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/common/src/geodb.ts b/common/src/geodb.ts index a1e73974..081c8b45 100644 --- a/common/src/geodb.ts +++ b/common/src/geodb.ts @@ -58,3 +58,9 @@ export function getLocationText( return `${city}${stateOrCountry && ', '}${stateOrCountry}` } + +export function getGoogleMapsUrl(locationText: string) { + let text = locationText.split(', ').join('+') + text = text.replace(' ', '+') + return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(text)}` +} diff --git a/tests/e2e/web/pages/settingsPage.ts b/tests/e2e/web/pages/settingsPage.ts index 6c730c8a..5b71eda3 100644 --- a/tests/e2e/web/pages/settingsPage.ts +++ b/tests/e2e/web/pages/settingsPage.ts @@ -29,7 +29,7 @@ export class SettingsPage { this.themeToggle = page.getByTestId('settings-dark-light-toggle') this.fontPicker = page.getByTestId('settings-font-picker') this.downloadProfileJSONDataButton = page.getByRole('button', { - name: 'Download all my data (JSON)', + name: 'Download all my data', }) this.manageHiddenProfilesButton = page.getByRole('button', {name: 'Manage hidden profiles'}) this.hiddenProfilesSection = page.getByTestId('hidden-profiles') diff --git a/web/components/answers/answer-compatibility-question-button.tsx b/web/components/answers/answer-compatibility-question-button.tsx index b9b5ad27..cd0c7d3f 100644 --- a/web/components/answers/answer-compatibility-question-button.tsx +++ b/web/components/answers/answer-compatibility-question-button.tsx @@ -42,7 +42,7 @@ export function AnswerCompatibilityQuestionButton(props: { onClick={() => setOpen(true)} color="none" className={ - 'px-3 py-2 rounded-md border border-primary-600 text-ink-700 hover:bg-primary-50 hover:text-ink-900' + 'px-3 py-2 rounded-md border border-primary-600 text-primary-800 hover:bg-primary-50' } > {t('answers.answer.cta', 'Answer{core} Questions', { @@ -78,7 +78,7 @@ export function CompatibilityPageButton() { return ( {t('answers.answer.view_list', 'View List of Questions')} diff --git a/web/components/answers/compatibility-questions-display.tsx b/web/components/answers/compatibility-questions-display.tsx index 8fe4a52d..73d5d81f 100644 --- a/web/components/answers/compatibility-questions-display.tsx +++ b/web/components/answers/compatibility-questions-display.tsx @@ -26,7 +26,6 @@ import { import {Col} from 'web/components/layout/col' import {Modal, MODAL_CLASS, SCROLLABLE_MODAL_CLASS} from 'web/components/layout/modal' import {Row} from 'web/components/layout/row' -import {CompatibleBadge} from 'web/components/widgets/compatible-badge' import {Input} from 'web/components/widgets/input' import {Linkify} from 'web/components/widgets/linkify' import {Pagination} from 'web/components/widgets/pagination' @@ -45,6 +44,7 @@ import {useUser} from 'web/hooks/use-user' import {useT} from 'web/lib/locale' import {db} from 'web/lib/supabase/db' +import {CompatibleBadge} from '../widgets/compatible-badge' import {Subtitle} from '../widgets/profile-subtitle' import { AnswerCompatibilityQuestionButton, @@ -57,13 +57,12 @@ import { deleteCompatibilityAnswer, getEmptyAnswer, IMPORTANCE_CHOICES, - IMPORTANCE_DISPLAY_COLORS, submitCompatibilityAnswer, } from './answer-compatibility-question-content' import {PreferredList, PreferredListNoComparison} from './compatibility-question-preferred-list' import {PinQuestionButton} from './pin-question-button' -const NUM_QUESTIONS_TO_SHOW = 4 +const NUM_QUESTIONS_TO_SHOW = 8 const NUM_PINNED_QUESTIONS_TO_SHOW = 4 export function separateQuestionsArray( @@ -213,18 +212,6 @@ export function CompatibilityQuestionsDisplay(props: { return ( - - - {isCurrentUser - ? t('answers.display.your_prompts', 'Compatibility Prompts') - : t('answers.display.user_prompts', 'Compatibility Prompts', { - name: shortenName(user.name), - })} - - {compatibilityScore && ( - - )} - {pinnedAnswers.length > 0 && ( @@ -254,7 +241,7 @@ export function CompatibilityQuestionsDisplay(props: { )} {answeredQuestions.length > 0 && ( -
+
{/* + {compatibilityScore && ( + + )} {answeredQuestions.length <= 0 ? ( @@ -391,6 +381,7 @@ export function CompatibilityAnswerBlock(props: { refreshCompatibilityAll: () => void fromProfilePage?: Profile showCommunityInfo?: boolean + className?: string }) { const { answer, @@ -400,6 +391,7 @@ export function CompatibilityAnswerBlock(props: { isCurrentUser, refreshCompatibilityAll, fromProfilePage, + className, } = props const showCommunityInfo = props.showCommunityInfo === undefined ? true : props.showCommunityInfo @@ -452,12 +444,13 @@ export function CompatibilityAnswerBlock(props: { return ( {question.question} @@ -535,18 +528,21 @@ export function CompatibilityAnswerBlock(props: { {answerText && ( {answerText} )} - + {answer?.explanation && } {distinctPreferredAnswersText.length > 0 && ( -
+
{preferredDoesNotIncludeAnswerText ? t('answers.display.acceptable', 'Acceptable') : t('answers.display.also_acceptable', 'Also acceptable')} @@ -556,7 +552,10 @@ export function CompatibilityAnswerBlock(props: { data-testid="profile-compatibility-question-acceptable-answer" > {distinctPreferredAnswersText.map((text) => ( - + {text} ))} @@ -716,10 +715,10 @@ function CompatibilityDisplay(props: { diff --git a/web/components/back-button.tsx b/web/components/back-button.tsx index 6fc28c98..345f8154 100644 --- a/web/components/back-button.tsx +++ b/web/components/back-button.tsx @@ -17,7 +17,7 @@ export function BackButton(props: {className?: string}) { ) : ( - + )} diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index fca7da59..4bb5a7dd 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -311,7 +311,7 @@ export const OptionalProfileUserForm = (props: { return ( <> - +

{t( 'profile.optional.subtitle', diff --git a/web/components/photos-modal.tsx b/web/components/photos-modal.tsx index 8f1a13c7..432d48b8 100644 --- a/web/components/photos-modal.tsx +++ b/web/components/photos-modal.tsx @@ -1,7 +1,6 @@ import {Profile} from 'common/profiles/profile' import {User} from 'common/user' import {useState} from 'react' -import {Button} from 'web/components/buttons/button' import {Col} from 'web/components/layout/col' import {Modal} from 'web/components/layout/modal' import {ShareProfileButtons} from 'web/components/widgets/share-profile-button' @@ -75,9 +74,25 @@ export const ViewProfileCardButton = (props: { const username = user.username return ( <> - + diff --git a/web/components/profile-about.tsx b/web/components/profile-about.tsx index ca809e4c..eab2611c 100644 --- a/web/components/profile-about.tsx +++ b/web/components/profile-about.tsx @@ -15,22 +15,12 @@ import { } from 'common/choices' import {MAX_INT, MIN_INT} from 'common/constants' import {convertGenderPlural, Gender} from 'common/gender' -import {getLocationText} from 'common/geodb' +import {getGoogleMapsUrl, getLocationText} from 'common/geodb' import {formatHeight, MeasurementSystem} from 'common/measurement-utils' import {Profile} from 'common/profiles/profile' import {Socials} from 'common/socials' import {UserActivity} from 'common/user' -import { - BarChart2, - Brain, - Briefcase, - HandHeart, - Home, - Languages, - Leaf, - Salad, - Sparkles, -} from 'lucide-react' +import {Home, Languages, Leaf, Salad} from 'lucide-react' import React, {ReactNode} from 'react' import {BiSolidDrink} from 'react-icons/bi' import {FaHeart, FaUsers} from 'react-icons/fa' @@ -39,12 +29,13 @@ import {FiUser} from 'react-icons/fi' import {GiRing} from 'react-icons/gi' import {HiOutlineGlobe} from 'react-icons/hi' import {LuBriefcase, LuCigarette, LuCigaretteOff, LuGraduationCap} from 'react-icons/lu' -import {MdNoDrinks, MdOutlineChildFriendly} from 'react-icons/md' +import {MdNoDrinks} from 'react-icons/md' import {PiHandsPrayingBold, PiMagnifyingGlassBold} from 'react-icons/pi' import {RiScales3Line} from 'react-icons/ri' import {TbBulb, TbCheck, TbMoodSad, TbUsers} from 'react-icons/tb' import {Col} from 'web/components/layout/col' import {Row} from 'web/components/layout/row' +import {CustomLink} from 'web/components/links' import {UserHandles} from 'web/components/user/user-handles' import {useChoicesContext} from 'web/hooks/use-choices' import {CustomMushroom} from 'web/lib/icons/mushroom' @@ -54,6 +45,19 @@ import {convertRace} from 'web/lib/util/convert-types' import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text' import {fromNow} from 'web/lib/util/time' +function Divider() { + return ( +

+ ) +} + export function AboutRow(props: { icon: ReactNode text?: string | null | string[] @@ -86,11 +90,20 @@ export function AboutRow(props: { } return ( - -
{icon}
+ +
+ {icon} +
{children} - {suffix &&
{suffix}
} + {suffix && ( +
+ {suffix} +
+ )}
) @@ -102,96 +115,218 @@ export default function ProfileAbout(props: { isCurrentUser: boolean }) { const {profile, userActivity, isCurrentUser} = props - const t = useT() - const choices = useChoicesContext() - const {locale} = useLocale() return ( - - - + + - - } - text={ - profile.work - ?.map((id) => choices?.['work']?.[id]) - .filter(Boolean) - .sort((a, b) => a.localeCompare(b, locale)) as string[] - } - testId="profile-about-work-area" - /> - } - text={profile.political_beliefs?.map((belief) => - t(`profile.political.${belief}`, INVERTED_POLITICAL_CHOICES[belief]), - )} - suffix={profile.political_details && `"${profile.political_details}"`} - testId="profile-about-political" - /> - } - text={profile.religion?.map((belief) => - t(`profile.religion.${belief}`, INVERTED_RELIGION_CHOICES[belief]), - )} - suffix={profile.religious_beliefs && `"${profile.religious_beliefs}"`} - testId="profile-about-religious" - /> - } - text={ - profile.interests - ?.map((id) => choices?.['interests']?.[id]) - .filter(Boolean) - .sort((a, b) => a.localeCompare(b, locale)) as string[] - } - testId="profile-about-interests" - /> - } - text={ - profile.causes - ?.map((id) => choices?.['causes']?.[id]) - .filter(Boolean) - .sort((a, b) => a.localeCompare(b, locale)) as string[] - } - testId="profile-about-causes" - /> - } - text={profile.mbti ? INVERTED_MBTI_CHOICES[profile.mbti] : null} - testId="profile-about-personality" - /> - - } - text={profile.ethnicity - ?.filter((r) => r !== 'other') - ?.map((r: any) => t(`profile.race.${r}`, convertRace(r)))} - testId="profile-about-ethnicity" - /> + + + + - } - text={profile.diet?.map((e) => t(`profile.diet.${e}`, INVERTED_DIET_CHOICES[e]))} - testId="profile-about-diet" - /> - } - text={profile.languages?.map((v) => - t(`profile.language.${v}`, INVERTED_LANGUAGE_CHOICES[v]), - )} - testId="profile-about-languages" - /> - - - {!isCurrentUser && } - + + + + {!isCurrentUser && ( + <> + + + )} + + ) +} + +export function ProfileInterestsAndCauses(props: {profile: Profile}) { + const {profile} = props + const t = useT() + const choices = useChoicesContext() + const {locale} = useLocale() + + const interests = profile.interests + ?.map((id) => choices?.['interests']?.[id]) + .filter(Boolean) + .sort((a, b) => a.localeCompare(b, locale)) as string[] + + const causes = profile.causes + ?.map((id) => choices?.['causes']?.[id]) + .filter(Boolean) + .sort((a, b) => a.localeCompare(b, locale)) as string[] + + if (!interests?.length && !causes?.length) return null + + return ( + + {interests && interests.length > 0 && ( + <> + {/**/} + {/* {t('profile.interests', 'Interests')}*/} + {/*
*/} +
+ {interests.map((interest, i) => ( + + {interest} + + ))} +
+ + )} + {causes && causes.length > 0 && ( + <> + {interests && interests.length > 0 && ( +
+ )} +
+ {t('profile.causes', 'Causes')} +
+
+ {causes.map((cause, i) => ( + + {cause} + + ))} +
+ + )} + + ) +} + +export function ProfilePersonality(props: {profile: Profile}) { + const {profile} = props + + if (!profile.mbti && !profile.big5_agreeableness) return null + + // MBTI type name mapping + const MBTI_TYPE_NAMES: Record = { + INTJ: 'Architect', + INTP: 'Logician', + ENTJ: 'Commander', + ENTP: 'Debater', + INFJ: 'Advocate', + INFP: 'Mediator', + ENFJ: 'Protagonist', + ENFP: 'Campaigner', + ISTJ: 'Logistician', + ISFJ: 'Defender', + ESTJ: 'Executive', + ESFJ: 'Consul', + ISTP: 'Virtuoso', + ISFP: 'Adventurer', + ESTP: 'Entrepreneur', + ESFP: 'Entertainer', + } + + const mbtiType = profile.mbti ? INVERTED_MBTI_CHOICES[profile.mbti] : null + const mbtiTypeName = mbtiType ? MBTI_TYPE_NAMES[mbtiType] : null + + return ( + + {profile.mbti && ( +
+
+ MBTI +
+
+ {mbtiType} + {mbtiTypeName && ( + + {mbtiTypeName} + + )} +
+
+ )} + + + ) +} + +export function ProfileLinks(props: {profile: Profile}) { + const {profile} = props + const links = (profile.links ?? {}) as Socials + + if (!links || Object.keys(links).length === 0) return null + + return ( + + ) } @@ -236,34 +371,62 @@ export function getSeekingText(profile: Profile, t: any, short?: boolean | undef return `${getSeekingConnectionText(profile, t, short)} ${seekingGenderText} ${ageRangeText}` } -function Seeking(props: {profile: Profile}) { +function SeekingAndRelationship(props: {profile: Profile}) { const t = useT() const {profile} = props - const text = getSeekingText(profile, t) - return ( - } - text={text} - testId="profile-about-seeking" - /> - ) -} - -function RelationshipStatus(props: {profile: Profile}) { - const {profile} = props - const t = useT() + const seekingText = getSeekingText(profile, t) const relationship_status = profile.relationship_status ?? [] - if (relationship_status.length === 0) return - const key = relationship_status[0] as keyof typeof RELATIONSHIP_ICONS - const icon = RELATIONSHIP_ICONS[key] ?? FaHeart + + if (relationship_status.length === 0 && !seekingText) return null + + const relationshipText = + relationship_status.length > 0 + ? relationship_status + ?.map((v) => + t(`profile.relationship_status.${v}`, INVERTED_RELATIONSHIP_STATUS_CHOICES[v]), + ) + .join(', ') + : null + + // const key = relationship_status[0] as keyof typeof RELATIONSHIP_ICONS + const icon = null // RELATIONSHIP_ICONS[key] ?? FaHeart + return ( - - t(`profile.relationship_status.${v}`, INVERTED_RELATIONSHIP_STATUS_CHOICES[v]), - )} - testId="profile-about-relationship-status" - /> + <> + +
+ {icon ? ( + React.createElement(icon, {className: 'h-5 w-5'}) + ) : ( + + )} +
+ +
+ {t('profile.connection_goals', 'Connection Goals')} +
+
{seekingText}
+ {relationshipText && ( +
+ {relationshipText} +
+ )} + +
+ + ) } @@ -287,35 +450,227 @@ function Education(props: {profile: Profile}) { if (text.length === 0) { return <> } + return ( - } - text={text} - testId="profile-about-education" - /> + <> + +
+ +
+ +
+ {t('profile.education', 'Education')} +
+
{text}
+ +
+ + ) } -function Occupation(props: {profile: Profile}) { +function OccupationAndWork(props: {profile: Profile}) { const t = useT() const {profile} = props + const choices = useChoicesContext() + const {locale} = useLocale() + const occupation_title = profile.occupation_title const company = profile.company + const workAreas = profile.work + ?.map((id) => choices?.['work']?.[id]) + .filter(Boolean) + .sort((a, b) => a.localeCompare(b, locale)) as string[] - if (!company && !occupation_title) { + if (!company && !occupation_title && !workAreas?.length) { return <> } + const occupationText = `${ occupation_title ? capitalizeAndRemoveUnderscores(occupation_title) : '' }${occupation_title && company ? ` ${t('profile.at', 'at')} ` : ''}${ company ? capitalizeAndRemoveUnderscores(company) : '' }` + + const workText = workAreas?.join(' · ') + return ( - } - text={occupationText} - testId="profile-about-occupation" - /> + <> + +
+ +
+ +
+ {t('profile.work', 'Work')} +
+
{occupationText}
+ {workText && ( +
+ {workText} +
+ )} + +
+ + + ) +} + +function Politics(props: {profile: Profile}) { + const t = useT() + const {profile} = props + const politicalBeliefs = profile.political_beliefs + const politicalDetails = profile.political_details + + if (!politicalBeliefs || politicalBeliefs.length === 0) return null + + const text = politicalBeliefs + .map((belief) => t(`profile.political.${belief}`, INVERTED_POLITICAL_CHOICES[belief])) + .join(', ') + + return ( + <> + +
+ +
+ +
+ {t('profile.politics', 'Politics')} +
+
{text}
+ {politicalDetails && ( +
+ "{politicalDetails}" +
+ )} + +
+ + + ) +} + +function Religion(props: {profile: Profile}) { + const t = useT() + const {profile} = props + const religion = profile.religion + const religiousBeliefs = profile.religious_beliefs + + if (!religion || religion.length === 0) return null + + const text = religion + .map((belief) => t(`profile.religion.${belief}`, INVERTED_RELIGION_CHOICES[belief])) + .join(', ') + + return ( + <> + +
+ +
+ +
+ {t('profile.religion', 'Religion')} +
+
{text}
+ {religiousBeliefs && ( +
+ "{religiousBeliefs}" +
+ )} + +
+ + + ) +} + +function Ethnicity(props: {profile: Profile}) { + const t = useT() + const {profile} = props + const ethnicity = profile.ethnicity?.filter((r) => r !== 'other') + + if (!ethnicity || ethnicity.length === 0) return null + + const text = ethnicity.map((r: any) => t(`profile.race.${r}`, convertRace(r))).join(', ') + + return ( + <> + +
+ +
+ +
+ {t('profile.ethnicity', 'Ethnicity')} +
+
{text}
+ +
+ + ) } @@ -324,17 +679,40 @@ function Smoker(props: {profile: Profile}) { const {profile} = props const isSmoker = profile.is_smoker if (isSmoker == null) return null - if (isSmoker) { - return ( - } text={t('profile.smokes', 'Smokes')} /> - ) - } + const text = isSmoker ? t('profile.smokes', 'Smokes') : t('profile.doesnt_smoke', "Doesn't smoke") + const icon = isSmoker ? ( + + ) : ( + + ) + return ( - } - text={t('profile.doesnt_smoke', "Doesn't smoke")} - testId="profile-about-smoker" - /> + <> + +
+ {icon} +
+ +
+ {t('profile.smoking', 'Smoking')} +
+
{text}
+ +
+ + ) } @@ -343,27 +721,125 @@ function Drinks(props: {profile: Profile}) { const {profile} = props const drinksPerMonth = profile.drinks_per_month if (drinksPerMonth == null) return null - if (drinksPerMonth === 0) { - return ( - } - text={t('profile.doesnt_drink', "Doesn't drink")} - testId="profile-about-not-drink" - /> - ) - } + + const text = + drinksPerMonth === 0 + ? t('profile.doesnt_drink', "Doesn't drink") + : drinksPerMonth === 1 + ? t('profile.drinks_one', '1 drink per month') + : t('profile.drinks_many', '{count} drinks per month', { + count: drinksPerMonth, + }) + const icon = + drinksPerMonth === 0 ? : + return ( - } - text={ - drinksPerMonth === 1 - ? t('profile.drinks_one', '1 drink per month') - : t('profile.drinks_many', '{count} drinks per month', { - count: drinksPerMonth, - }) - } - testId="profile-about-drinker" - /> + <> + +
+ {icon} +
+ +
+ {t('profile.alcohol', 'Alcohol')} +
+
{text}
+ +
+ + + ) +} + +function Diet(props: {profile: Profile}) { + const t = useT() + const {profile} = props + const diet = profile.diet + + if (!diet || diet.length === 0) return null + + const text = diet.map((e) => t(`profile.diet.${e}`, INVERTED_DIET_CHOICES[e])).join(', ') + + return ( + <> + +
+ +
+ +
+ {t('profile.diet', 'Diet')} +
+
{text}
+ +
+ + + ) +} + +function LanguagesSection(props: {profile: Profile}) { + const t = useT() + const {profile} = props + const languages = profile.languages + + if (!languages || languages.length === 0) return null + + const text = languages + .map((v) => t(`profile.language.${v}`, INVERTED_LANGUAGE_CHOICES[v])) + .join(', ') + + return ( + <> + +
+ +
+ +
+ {t('profile.languages', 'Languages')} +
+
{text}
+ +
+ + ) } @@ -373,20 +849,31 @@ function Cannabis(props: {profile: Profile}) { const cannabis = profile.cannabis if (!cannabis) return null - const parts: string[] = [] + const parts = t(`profile.cannabis.${cannabis}`, INVERTED_CANNABIS_CHOICES[cannabis]) - // Name - parts.push(t('profile.cannabis.label', 'Cannabis:')) - - // Frequency - parts.push(t(`profile.cannabis.${cannabis}`, INVERTED_CANNABIS_CHOICES[cannabis])) - - // Intention (if not "never" and has intentions) + // Intention chips (if not "never" and has intentions) + let intentionChips: React.ReactNode | null = null if (cannabis !== 'never_not_interested' && profile.cannabis_intention?.length) { - const intentions = profile.cannabis_intention - .map((i) => t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])) - .join(', ') - parts.push(`(${intentions})`) + intentionChips = ( +
+ {profile.cannabis_intention.map((i) => ( + + {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])} + + ))} +
+ ) } // Preference for partner @@ -404,12 +891,38 @@ function Cannabis(props: {profile: Profile}) { } return ( - } - text={parts.join(' ')} - testId="profile-about-cannabis" - suffix={suffix} - /> + <> + +
+ +
+ +
+ {t('profile.cannabis', 'Cannabis')} +
+
{parts}
+ {intentionChips} + {suffix && ( +
+ {suffix} +
+ )} + +
+ + ) } @@ -419,20 +932,34 @@ function Psychedelics(props: {profile: Profile}) { const psychedelics = profile.psychedelics if (!psychedelics) return null - const parts: string[] = [] + const parts = t( + `profile.psychedelics.${psychedelics}`, + INVERTED_PSYCHEDELICS_CHOICES[psychedelics], + ) - // Name - parts.push(t('profile.psychedelics.label', 'Psychedelics:')) - - // Frequency - parts.push(t(`profile.psychedelics.${psychedelics}`, INVERTED_PSYCHEDELICS_CHOICES[psychedelics])) - - // Intention (if not "never" and has intentions) + // Intention chips (if not "never" and has intentions) + let intentionChips: React.ReactNode | null = null if (psychedelics !== 'never_not_interested' && profile.psychedelics_intention?.length) { - const intentions = profile.psychedelics_intention - .map((i) => t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])) - .join(', ') - parts.push(`(${intentions})`) + intentionChips = ( +
+ {profile.psychedelics_intention.map((i) => ( + + {t(`profile.substance_intention.${i}`, INVERTED_SUBSTANCE_INTENTION_CHOICES[i])} + + ))} +
+ ) } // Preference for partner @@ -450,39 +977,65 @@ function Psychedelics(props: {profile: Profile}) { } return ( - } - text={parts.join(' ')} - testId="profile-about-psychedelics" - suffix={suffix} - /> + <> + +
+ +
+ +
+ {t('profile.psychedelics', 'Psychedelics')} +
+
{parts}
+ {intentionChips} + {suffix && ( +
+ {suffix} +
+ )} + +
+ + ) } -function WantsKids(props: {profile: Profile}) { - const t = useT() - const {profile} = props - const wantsKidsStrength = profile.wants_kids_strength - if (wantsKidsStrength == null || wantsKidsStrength < 0) return null - const wantsKidsText = - wantsKidsStrength == 0 - ? t('profile.wants_kids_0', 'Does not want children') - : wantsKidsStrength == 1 - ? t('profile.wants_kids_1', 'Prefers not to have children') - : wantsKidsStrength == 2 - ? t('profile.wants_kids_2', 'Neutral or open to having children') - : wantsKidsStrength == 3 - ? t('profile.wants_kids_3', 'Leaning towards wanting children') - : t('profile.wants_kids_4', 'Wants children') - - return ( - } - text={wantsKidsText} - testId="profile-about-wants-kids" - /> - ) -} +// function WantsKids(props: {profile: Profile}) { +// const t = useT() +// const {profile} = props +// const wantsKidsStrength = profile.wants_kids_strength +// if (wantsKidsStrength == null || wantsKidsStrength < 0) return null +// const wantsKidsText = +// wantsKidsStrength == 0 +// ? t('profile.wants_kids_0', 'Does not want children') +// : wantsKidsStrength == 1 +// ? t('profile.wants_kids_1', 'Prefers not to have children') +// : wantsKidsStrength == 2 +// ? t('profile.wants_kids_2', 'Neutral or open to having children') +// : wantsKidsStrength == 3 +// ? t('profile.wants_kids_3', 'Leaning towards wanting children') +// : t('profile.wants_kids_4', 'Wants children') +// +// return ( +// } +// text={wantsKidsText} +// testId="profile-about-wants-kids" +// /> +// ) +// } function LastOnline(props: {lastOnlineTime?: string}) { const t = useT() @@ -544,63 +1097,161 @@ function Big5Traits(props: {profile: Profile}) { } return ( - }> - -
{t('profile.big5', 'Big Five personality traits')}
-
- {traits.map((trait) => { - if (trait.value === null || trait.value === undefined) return null + +
+ {t('profile.big5', 'Big Five')} +
+
+ {traits.map((trait) => { + if (trait.value === null || trait.value === undefined) return null - return ( - - {/*
{trait.icon}
*/} -
{trait.label}
-
-
-
-
{trait.value}
- - ) - })} -
- - + const isHigh = trait.value >= 70 + const isLow = trait.value <= 30 + + return ( +
+
+ {trait.label} + + {trait.value} + +
+
+
+
+
+ ) + })} +
+ ) } -function HasKids(props: {profile: Profile}) { +function CombinedChildren(props: {profile: Profile}) { const t = useT() const {profile} = props - if (typeof profile.has_kids !== 'number') return null + const hasKidsText = - profile.has_kids == 0 - ? t('profile.has_kids.doesnt_have_kids', 'Does not have children') - : profile.has_kids > 1 - ? t('profile.has_kids_many', 'Has {count} kids', { - count: profile.has_kids, - }) - : t('profile.has_kids_one', 'Has {count} kid', { - count: profile.has_kids, - }) - const faChild = - const icon = - profile.has_kids === 0 ? ( -
- {faChild} -
- {/*
*/} -
+ typeof profile.has_kids === 'number' + ? profile.has_kids == 0 + ? t('profile.has_kids.doesnt_have_kids', 'Does not have children') + : profile.has_kids > 1 + ? t('profile.has_kids_many', 'Has {count} kids', { + count: profile.has_kids, + }) + : t('profile.has_kids_one', 'Has {count} kid', { + count: profile.has_kids, + }) + : null + + const wantsKidsStrength = profile.wants_kids_strength + const wantsKidsText = + wantsKidsStrength != null && wantsKidsStrength >= 0 + ? wantsKidsStrength == 0 + ? t('profile.wants_kids_0', 'Does not want children') + : wantsKidsStrength == 1 + ? t('profile.wants_kids_1', 'Prefers not to have children') + : wantsKidsStrength == 2 + ? t('profile.wants_kids_2', 'Neutral or open to having children') + : wantsKidsStrength == 3 + ? t('profile.wants_kids_3', 'Leaning towards wanting children') + : t('profile.wants_kids_4', 'Wants children') + : null + + if (!hasKidsText && !wantsKidsText) return null + + return ( + <> + +
+
-
- ) : ( - faChild - ) - return + +
+ {t('profile.children', 'Children')} +
+
{hasKidsText}
+ {wantsKidsText && ( +
+ {wantsKidsText} +
+ )} + + + + + + ) } +// function HasKids(props: {profile: Profile}) { +// const t = useT() +// const {profile} = props +// if (typeof profile.has_kids !== 'number') return null +// const hasKidsText = +// profile.has_kids == 0 +// ? t('profile.has_kids.doesnt_have_kids', 'Does not have children') +// : profile.has_kids > 1 +// ? t('profile.has_kids_many', 'Has {count} kids', { +// count: profile.has_kids, +// }) +// : t('profile.has_kids_one', 'Has {count} kid', { +// count: profile.has_kids, +// }) +// const faChild = +// const icon = +// profile.has_kids === 0 ? ( +//
+// {faChild} +//
+// {/*
*/} +//
+//
+//
+// ) : ( +// faChild +// ) +// return +// } + function RaisedIn(props: {profile: Profile}) { const t = useT() const locationText = getLocationText(props.profile, 'raised_in_') @@ -608,10 +1259,36 @@ function RaisedIn(props: {profile: Profile}) { return null } return ( - } - text={t('profile.about.raised_in', `Raised in ${locationText}`, {location: locationText})} - /> + <> + +
+ +
+ +
+ {t('profile.raised_in', 'Raised In')} +
+
+ + {locationText} + +
+ +
+ + ) } diff --git a/web/components/profile-carousel.tsx b/web/components/profile-carousel.tsx index 78d1fe44..b357e164 100644 --- a/web/components/profile-carousel.tsx +++ b/web/components/profile-carousel.tsx @@ -65,7 +65,7 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile src={url} height={300} width={300} - className="h-full w-full cursor-pointer rounded object-cover" + className="h-full w-full cursor-pointer rounded-xl object-cover" autoPlay muted loop @@ -83,7 +83,7 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile width={300} sizes="(max-width: 640px) 100vw, 300px" alt="" - className="h-full cursor-pointer rounded object-cover" + className="h-full cursor-pointer rounded-xl object-cover" onClick={() => { setLightboxUrl(url) setLightboxOpen(true) @@ -91,9 +91,11 @@ export default function ProfileCarousel(props: {profile: Profile; refreshProfile /> )}
-

- {(profile.image_descriptions as Record)?.[url]} -

+ {(profile.image_descriptions as Record)?.[url] && ( +

+ {(profile.image_descriptions as Record)?.[url]} +

+ )} ))} diff --git a/web/components/profile-comment-section.tsx b/web/components/profile-comment-section.tsx index e3754174..b22ad427 100644 --- a/web/components/profile-comment-section.tsx +++ b/web/components/profile-comment-section.tsx @@ -12,8 +12,6 @@ import {useLiveCommentsOnProfile} from 'web/hooks/use-comments-on-profile' import {updateProfile} from 'web/lib/api' import {useT} from 'web/lib/locale' -import {Subtitle} from './widgets/profile-subtitle' - export const ProfileCommentSection = (props: { onUser: User profile: Profile @@ -26,14 +24,14 @@ export const ProfileCommentSection = (props: { const parentComments = comments.filter((c) => !c.replyToCommentId) const commentsByParent = groupBy(comments, (c) => c.replyToCommentId ?? '_') const [profile, setProfile] = useState(props.profile) + const [showCommentInput, setShowCommentInput] = useState(false) const isCurrentUser = currentUser?.id === onUser.id if (!currentUser && (!profile.comments_enabled || parentComments.length == 0)) return null return ( - - {t('profile.comments.section_title', 'Endorsements')} + {isCurrentUser && !simpleView && ( {currentUser && profile.comments_enabled && ( <> -
+
{isCurrentUser ? ( <> {t( @@ -79,11 +77,30 @@ export const ProfileCommentSection = (props: { )}
{!isCurrentUser && ( - + <> + {!showCommentInput ? ( + + ) : ( + + )} + )} )} diff --git a/web/components/profile-grid.tsx b/web/components/profile-grid.tsx index d449e92f..57bb9fdb 100644 --- a/web/components/profile-grid.tsx +++ b/web/components/profile-grid.tsx @@ -334,6 +334,7 @@ function ProfilePreview(props: { 'hover:before:opacity-100', 'hover:bg-canvas-100', 'relative z-10 cursor-pointer group block rounded-lg overflow-hidden bg-transparent h-full border border-canvas-300', + 'text-ink-600', // hover, )} > @@ -405,7 +406,12 @@ function ProfilePreview(props: { ?.slice(0, 10) ?.map(capitalizePure) ?.map((tag, i) => ( - + {tag.trim()} ))} diff --git a/web/components/profile/connect-actions.tsx b/web/components/profile/connect-actions.tsx index f929d6ce..29b60454 100644 --- a/web/components/profile/connect-actions.tsx +++ b/web/components/profile/connect-actions.tsx @@ -6,7 +6,6 @@ import ReactMarkdown from 'react-markdown' import {Col} from 'web/components/layout/col' import {Row} from 'web/components/layout/row' import {SendMessageButton} from 'web/components/messaging/send-message-button' -import {Subtitle} from 'web/components/widgets/profile-subtitle' import {Tooltip} from 'web/components/widgets/tooltip' import {useProfile} from 'web/hooks/use-profile' import {useUser} from 'web/hooks/use-user' @@ -83,10 +82,8 @@ export function ConnectActions(props: {profile: Profile; user: User}) { if (isCurrentUser || !currentUser) return null return ( - -
- {t('profile.connect.title', 'Connect')} - + +
{/* Primary Action */}
{profile.allow_direct_messaging || matches.length > 0 ? ( @@ -109,9 +106,9 @@ export function ConnectActions(props: {profile: Profile; user: User}) { {/* Interest Section */}
-
+

{t('profile.connect.private_connection_signal', 'Private connection signal')} -

+ {profile.allow_interest_indicating ? ( <> diff --git a/web/components/profile/profile-header.tsx b/web/components/profile/profile-header.tsx index a41fff4c..8566073b 100644 --- a/web/components/profile/profile-header.tsx +++ b/web/components/profile/profile-header.tsx @@ -8,11 +8,11 @@ import clsx from 'clsx' import {debug} from 'common/logger' import {Profile} from 'common/profiles/profile' import {User, UserActivity} from 'common/user' +import Image from 'next/image' import Link from 'next/link' import Router from 'next/router' import React from 'react' import toast from 'react-hot-toast' -import {Button} from 'web/components/buttons/button' import {MoreOptionsUserButton} from 'web/components/buttons/more-options-user-button' import DropdownMenu from 'web/components/comments/dropdown-menu' import {Col} from 'web/components/layout/col' @@ -52,52 +52,82 @@ export default function ProfileHeader(props: { }) return ( - - - {currentUser && !isCurrentUser && isHiddenFromMe && ( -
- {t( - 'profile_grid.hidden_notice', - "You hid this person, so they don't appear in your search results.", - )} -
- )} - {currentUser && isCurrentUser && disabled && ( -
- {t( - 'profile.header.disabled_notice', - 'You disabled your profile, so no one else can access it.', - )} -
- )} - - - - - {/*{!isCurrentUser && }*/} - - {simpleView ? ( - - {user.name} - - ) : ( - {user.name} - )} - + + {currentUser && !isCurrentUser && isHiddenFromMe && ( +
+ {t( + 'profile_grid.hidden_notice', + "You hid this person, so they don't appear in your search results.", + )} +
+ )} + {currentUser && isCurrentUser && disabled && ( +
+ {t( + 'profile.header.disabled_notice', + 'You disabled your profile, so no one else can access it.', + )} +
+ )} + + + {profile.pinned_url && ( +
+ +
+ )} + + + + + + {/*{!isCurrentUser && }*/} + + {simpleView ? ( + + + {user.name} + + + ) : ( + + {user.name} + + )} + + + + - - - +
+
- - + {profile.keywords?.map(capitalizePure)?.map((tag, i) => ( {tag.trim()} @@ -106,8 +136,31 @@ export default function ProfileHeader(props: { {profile.headline && ( -
- "{profile.headline}" +
+
+ + " + + {profile.headline} + + " + +
)} @@ -159,21 +212,20 @@ export function ProfileHeaderActions(props: { if (currentUser && isCurrentUser) { return ( - + - + +