diff --git a/common/messages/de.json b/common/messages/de.json index 99f5a9c4..7a05a20b 100644 --- a/common/messages/de.json +++ b/common/messages/de.json @@ -494,6 +494,12 @@ "messages.you_prefix": "Sie: ", "messaging.email_verification_required": "Sie müssen Ihre E-Mail überprüfen, um Personen zu schreiben.", "messaging.send_thoughtful_message": "Sende ihnen eine durchdachte Nachricht", + "send_message.placeholder": "Was hat dich an {name}s Profil wirklich angesprochen? Was möchtest du gemeinsam erkunden?", + "send_message.title": "Beginne ein bedeutungsvolles Gespräch", + "send_message.guidance": "Compass geht um Tiefe. Nimm dir einen Moment, um etwas Echtes zu schreiben.", + "send_message.keywords_hint": "Füge einige von {name}s Themen in deine Nachricht ein", + "send_message.more_chars": "{count} Zeichen mehr", + "send_message.ready": "Bereit zu senden", "more_options_user.ban_user": "Benutzer sperren", "more_options_user.banned": "Gesperrt", "more_options_user.banning": "Sperre...", @@ -1130,7 +1136,6 @@ "security.seo.title": "Sicherheit", "security.title": "Sicherheit", "send_message.button_label": "Nachricht", - "send_message.title": "Kontakt", "settings.action.cancel": "Abbrechen", "settings.action.save": "Speichern", "settings.connection_preferences.description": "Kontrollieren Sie, wie andere sich mit Ihnen verbinden können.", diff --git a/common/messages/fr.json b/common/messages/fr.json index cc73424c..aa40a2fa 100644 --- a/common/messages/fr.json +++ b/common/messages/fr.json @@ -494,6 +494,12 @@ "messages.you_prefix": "Vous : ", "messaging.email_verification_required": "Vous devez vérifier votre e-mail pour pouvoir envoyer des messages.", "messaging.send_thoughtful_message": "Envoyez-leur un message réfléchi", + "send_message.placeholder": "Qu'est-ce qui vous a vraiment touché dans le profil de {name} ? Qu'aimeriez-vous explorer ensemble ?", + "send_message.title": "Commencez une conversation qui a du sens", + "send_message.guidance": "Prenez un moment pour écrire quelque chose d'authentique.", + "send_message.keywords_hint": "Insérez quelques-uns des sujets de {name} dans votre message", + "send_message.more_chars": "{count} caractères de plus", + "send_message.ready": "Prêt à envoyer", "more_options_user.ban_user": "Bannir l'utilisateur", "more_options_user.banned": "Banni", "more_options_user.banning": "Bannissement...", @@ -1129,7 +1135,6 @@ "security.seo.title": "Sécurité", "security.title": "Sécurité", "send_message.button_label": "Contacter", - "send_message.title": "Contactez", "settings.action.cancel": "Annuler", "settings.action.save": "Enregistrer", "settings.connection_preferences.description": "Contrôlez comment les autres peuvent se connecter avec vous.", diff --git a/web/components/comments/comment-input.tsx b/web/components/comments/comment-input.tsx index 9c320393..963f277d 100644 --- a/web/components/comments/comment-input.tsx +++ b/web/components/comments/comment-input.tsx @@ -131,6 +131,7 @@ export function CommentInputTextArea(props: { cancelEditing?: () => void isSubmitting: boolean submitOnEnter?: boolean + isDisabled?: boolean isEditing?: boolean commentTypes?: CommentType[] }) { @@ -144,6 +145,7 @@ export function CommentInputTextArea(props: { replyTo, commentTypes = ['comment'], cancelEditing, + isDisabled, } = props const t = useT() @@ -200,7 +202,7 @@ export function CommentInputTextArea(props: { {user && !isSubmitting && submit && commentTypes.includes('repost') && ( )} - {!isSubmitting && submit && commentTypes.includes('comment') && ( + {!isSubmitting && !isDisabled && submit && commentTypes.includes('comment') && ( + ))} + + + )} + + + {/* quality meter */} +
+
+
+
+ + {charCount < MIN_CHARS + ? t('send_message.more_chars', '{count} more characters', { + count: MIN_CHARS - charCount, + }) + : t('send_message.ready', 'Ready to send')} + +
+ ) : ( )} diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index 36b4d43d..50607504 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -45,7 +45,6 @@ import {Input} from 'web/components/widgets/input' import {RadioToggleGroup} from 'web/components/widgets/radio-toggle-group' import {Select} from 'web/components/widgets/select' import {Slider} from 'web/components/widgets/slider' -import {Title} from 'web/components/widgets/title' import {ChoiceMap, ChoiceSetter, useChoicesContext} from 'web/hooks/use-choices' import {api} from 'web/lib/api' import {useLocale, useT} from 'web/lib/locale' @@ -277,9 +276,13 @@ export const OptionalProfileUserForm = (props: { return ( <> - {t('profile.optional.subtitle', 'Optional information')} - +

+ {t( + 'profile.optional.subtitle', + 'Although all the fields below are optional, they will help people better understand you and connect with you.', + )} +

) : ( diff --git a/web/components/profile/profile-header.tsx b/web/components/profile/profile-header.tsx index 3a07b999..41393650 100644 --- a/web/components/profile/profile-header.tsx +++ b/web/components/profile/profile-header.tsx @@ -221,6 +221,7 @@ export default function ProfileHeader(props: { diff --git a/web/components/widgets/editor.tsx b/web/components/widgets/editor.tsx index 7e45b135..32bf2278 100644 --- a/web/components/widgets/editor.tsx +++ b/web/components/widgets/editor.tsx @@ -84,9 +84,11 @@ export function useTextEditor(props: { extensions?: Extensions className?: string onChange?: () => void + nRowsMin?: number }) { - const {placeholder, className, max, defaultValue, size = 'md', key, onChange} = props + const {placeholder, className, max, defaultValue, size = 'md', key, onChange, nRowsMin} = props const simple = size === 'sm' + const minRows = nRowsMin ?? (simple ? 2 : 3) const [content, setContent] = usePersistentLocalState( undefined, @@ -116,7 +118,7 @@ export function useTextEditor(props: { 'dark:[&_.ProseMirror-gapcursor]:after:border-white', // gap cursor className, ), - style: `min-height: ${1 + 1.625 * (simple ? 2 : 3)}em`, // 1em padding + 1.625 lines per row + style: `min-height: ${1 + 1.625 * minRows}em`, // 1em padding + 1.625 lines per row }, }) @@ -149,7 +151,7 @@ export function useTextEditor(props: { Placeholder.configure({ placeholder, emptyEditorClass: - 'before:content-[attr(data-placeholder)] before:text-ink-500 before:float-left before:h-0 cursor-text', + 'before:content-[attr(data-placeholder)] before:text-ink-1000/55 before:text-sm before:float-left before:h-0 cursor-text', }), CharacterCount.configure({limit: max}), ...(props.extensions ?? []), diff --git a/web/components/widgets/likes-display.tsx b/web/components/widgets/likes-display.tsx index f8e85cf3..d484371b 100644 --- a/web/components/widgets/likes-display.tsx +++ b/web/components/widgets/likes-display.tsx @@ -1,167 +1,167 @@ -import {UserIcon} from '@heroicons/react/24/solid' -import {LikeData, ShipData} from 'common/api/profile-types' -import {Profile} from 'common/profiles/profile' -import {keyBy, orderBy} from 'lodash' -import Image from 'next/image' -import Link from 'next/link' -import {Col} from 'web/components/layout/col' -import {SendMessageButton} from 'web/components/messaging/send-message-button' -import {Avatar, EmptyAvatar} from 'web/components/widgets/avatar' -import {Carousel} from 'web/components/widgets/carousel' -import {UserLink} from 'web/components/widgets/user-link' -import {useProfileByUserId} from 'web/hooks/use-profile' -import {useUser} from 'web/hooks/use-user' -import {useUserById} from 'web/hooks/use-user-supabase' - -import {Subtitle} from './profile-subtitle' -import {ShipsList} from './ships-display' - -export const LikesDisplay = (props: { - likesGiven: LikeData[] - likesReceived: LikeData[] - ships: ShipData[] - refreshShips: () => Promise - profileProfile: Profile -}) => { - const {likesGiven, likesReceived, ships, refreshShips, profileProfile} = props - - const likesGivenByUserId = keyBy(likesGiven, (l) => l.user_id) - const likesReceivedByUserId = keyBy(likesReceived, (l) => l.user_id) - const mutualLikeUserIds = Object.keys(likesGivenByUserId).filter( - (userId) => likesReceivedByUserId[userId], - ) - - const mutualLikes = mutualLikeUserIds.map((user_id) => { - const likeGiven = likesGivenByUserId[user_id] - const likeReceived = likesReceivedByUserId[user_id] - const created_time = Math.max(likeGiven.created_time, likeReceived.created_time) - return {user_id, created_time} - }) - const sortedMutualLikes = orderBy(mutualLikes, 'created_time', 'desc') - const onlyLikesGiven = likesGiven.filter((l) => !likesReceivedByUserId[l.user_id]) - const onlyLikesReceived = likesReceived.filter((l) => !likesGivenByUserId[l.user_id]) - - if ( - sortedMutualLikes.length === 0 && - onlyLikesReceived.length === 0 && - onlyLikesGiven.length === 0 && - ships.length === 0 - ) { - return null - } - - return ( - - {sortedMutualLikes.length > 0 && ( - - Mutual likes - - {sortedMutualLikes.map((like) => { - return ( - - ) - })} - - - )} - - {onlyLikesReceived.length > 0 && ( - - )} - {onlyLikesGiven.length > 0 && } - {ships.length > 0 && ( - - )} - - ) -} - -const LikesList = (props: {label: string; likes: LikeData[]}) => { - const {label, likes} = props - - const maxShown = 50 - const truncatedLikes = likes.slice(0, maxShown) - - return ( - - {label} - {truncatedLikes.length > 0 ? ( - - {truncatedLikes.map((like) => ( - - ))} - - ) : ( -
None
- )} - - ) -} - -const UserAvatar = (props: {userId: string; className?: string}) => { - const {userId, className} = props - const profile = useProfileByUserId(userId) - const user = useUserById(userId) - - // console.debug('UserAvatar', user?.username, profile?.pinned_url) - - if (!profile) return - return -} - -export const MatchTile = (props: {profileProfile: Profile; matchUserId: string}) => { - const {matchUserId, profileProfile} = props - const profile = useProfileByUserId(matchUserId) - const user = useUserById(matchUserId) - const currentUser = useUser() - const isYourMatch = currentUser?.id === profileProfile.user_id - - if (!profile || !user) return - const {pinned_url} = profile - - return ( - - - - - - {pinned_url ? ( - - {`${user.username}`} - - ) : ( - - - - )} - {isYourMatch && ( - - - - )} - - - ) -} +// import {UserIcon} from '@heroicons/react/24/solid' +// import {LikeData, ShipData} from 'common/api/profile-types' +// import {Profile} from 'common/profiles/profile' +// import {keyBy, orderBy} from 'lodash' +// import Image from 'next/image' +// import Link from 'next/link' +// import {Col} from 'web/components/layout/col' +// import {SendMessageButton} from 'web/components/messaging/send-message-button' +// import {Avatar, EmptyAvatar} from 'web/components/widgets/avatar' +// import {Carousel} from 'web/components/widgets/carousel' +// import {UserLink} from 'web/components/widgets/user-link' +// import {useProfileByUserId} from 'web/hooks/use-profile' +// import {useUser} from 'web/hooks/use-user' +// import {useUserById} from 'web/hooks/use-user-supabase' +// +// import {Subtitle} from './profile-subtitle' +// import {ShipsList} from './ships-display' +// +// export const LikesDisplay = (props: { +// likesGiven: LikeData[] +// likesReceived: LikeData[] +// ships: ShipData[] +// refreshShips: () => Promise +// profileProfile: Profile +// }) => { +// const {likesGiven, likesReceived, ships, refreshShips, profileProfile} = props +// +// const likesGivenByUserId = keyBy(likesGiven, (l) => l.user_id) +// const likesReceivedByUserId = keyBy(likesReceived, (l) => l.user_id) +// const mutualLikeUserIds = Object.keys(likesGivenByUserId).filter( +// (userId) => likesReceivedByUserId[userId], +// ) +// +// const mutualLikes = mutualLikeUserIds.map((user_id) => { +// const likeGiven = likesGivenByUserId[user_id] +// const likeReceived = likesReceivedByUserId[user_id] +// const created_time = Math.max(likeGiven.created_time, likeReceived.created_time) +// return {user_id, created_time} +// }) +// const sortedMutualLikes = orderBy(mutualLikes, 'created_time', 'desc') +// const onlyLikesGiven = likesGiven.filter((l) => !likesReceivedByUserId[l.user_id]) +// const onlyLikesReceived = likesReceived.filter((l) => !likesGivenByUserId[l.user_id]) +// +// if ( +// sortedMutualLikes.length === 0 && +// onlyLikesReceived.length === 0 && +// onlyLikesGiven.length === 0 && +// ships.length === 0 +// ) { +// return null +// } +// +// return ( +// +// {sortedMutualLikes.length > 0 && ( +// +// Mutual likes +// +// {sortedMutualLikes.map((like) => { +// return ( +// +// ) +// })} +// +// +// )} +// +// {onlyLikesReceived.length > 0 && ( +// +// )} +// {onlyLikesGiven.length > 0 && } +// {ships.length > 0 && ( +// +// )} +// +// ) +// } +// +// const LikesList = (props: {label: string; likes: LikeData[]}) => { +// const {label, likes} = props +// +// const maxShown = 50 +// const truncatedLikes = likes.slice(0, maxShown) +// +// return ( +// +// {label} +// {truncatedLikes.length > 0 ? ( +// +// {truncatedLikes.map((like) => ( +// +// ))} +// +// ) : ( +//
None
+// )} +// +// ) +// } +// +// const UserAvatar = (props: {userId: string; className?: string}) => { +// const {userId, className} = props +// const profile = useProfileByUserId(userId) +// const user = useUserById(userId) +// +// // console.debug('UserAvatar', user?.username, profile?.pinned_url) +// +// if (!profile) return +// return +// } +// +// export const MatchTile = (props: {profileProfile: Profile; matchUserId: string}) => { +// const {matchUserId, profileProfile} = props +// const profile = useProfileByUserId(matchUserId) +// const user = useUserById(matchUserId) +// const currentUser = useUser() +// const isYourMatch = currentUser?.id === profileProfile.user_id +// +// if (!profile || !user) return +// const {pinned_url} = profile +// +// return ( +// +// +// +// +// +// {pinned_url ? ( +// +// {`${user.username}`} +// +// ) : ( +// +// +// +// )} +// {isYourMatch && ( +// +// +// +// )} +// +// +// ) +// } diff --git a/web/lib/util/strings.ts b/web/lib/util/strings.ts new file mode 100644 index 00000000..88b27283 --- /dev/null +++ b/web/lib/util/strings.ts @@ -0,0 +1,6 @@ +/** + * Returns the possessive form of a name. + * Adds 's unless the name already ends with s, in which case just adds '. + */ +export const possessive = (name: string) => + name.endsWith('s') ? `${name}'` : `${name}'s`