Fix interest (etc.) shown as IDs instead of labels in saved searches and emails

This commit is contained in:
MartinBraquet
2026-01-27 23:04:06 +01:00
parent 0ac5ce7b41
commit 7ac933160d
8 changed files with 98 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@compass/api",
"description": "Backend API endpoints",
"version": "1.2.1",
"version": "1.2.2",
"private": true,
"scripts": {
"watch:serve": "tsx watch src/serve.ts",

View File

@@ -11,6 +11,7 @@ import {MatchesType} from "common/profiles/bookmarked_searches";
import NewSearchAlertsEmail from "email/new-search_alerts";
import WelcomeEmail from "email/welcome";
import * as admin from "firebase-admin";
import {getOptionsIdsToLabels} from "shared/supabase/options";
export const fromEmail = 'Compass <compass@compassmeet.com>'
@@ -111,6 +112,11 @@ export const sendSearchAlertsEmail = async (
const email = privateUser.email;
if (!email || !sendToEmail) return
// Determine locale (fallback to 'en') and load option labels before rendering
// TODO: add locale to user array
const locale = (toUser as any)?.locale ?? 'en'
const optionIdsToLabels = await getOptionsIdsToLabels(locale)
return await sendEmail({
from: fromEmail,
subject: `People aligned with your values just joined`,
@@ -121,6 +127,7 @@ export const sendSearchAlertsEmail = async (
matches={matches}
unsubscribeUrl={unsubscribeUrl}
email={email}
optionIdsToLabels={optionIdsToLabels}
/>
),
})

View File

@@ -1,9 +1,5 @@
import {
CreateEmailRequestOptions,
Resend,
type CreateEmailOptions,
} from 'resend'
import { log } from 'shared/utils'
import {type CreateEmailOptions, CreateEmailRequestOptions, Resend,} from 'resend'
import {log} from 'shared/utils'
import {sleep} from "common/util/time";
@@ -17,6 +13,12 @@ export const sendEmail = async (
const resend = getResend()
console.debug(resend, payload, options)
const skip = false
if (skip) {
console.warn("Skipping email send")
return null
}
if (!resend) return null
const { data, error } = await resend.emails.send(

View File

@@ -13,6 +13,7 @@ interface NewMessageEmailProps {
matches: MatchesType[]
unsubscribeUrl: string
email?: string
optionIdsToLabels?: Record<string, Record<string, string>>
}
export const NewSearchAlertsEmail = ({
@@ -20,6 +21,7 @@ export const NewSearchAlertsEmail = ({
unsubscribeUrl,
matches,
email,
optionIdsToLabels = {},
}: NewMessageEmailProps) => {
const name = toUser.name.split(' ')[0]
@@ -44,7 +46,8 @@ export const NewSearchAlertsEmail = ({
<Text style={{fontWeight: "bold", marginBottom: "5px"}}>
{formatFilters(
match.description.filters as Partial<FilterFields>,
match.description.location as locationType
match.description.location as locationType,
optionIdsToLabels,
)?.join(" • ")}
</Text>
<Text style={{margin: 0}}>

View File

@@ -0,0 +1,51 @@
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {OPTION_TABLES, OptionTableKey} from 'common/profiles/constants'
interface CacheEntry {
data: Record<OptionTableKey, Record<string, string>>
timestamp: number
}
const cache = new Map<string, CacheEntry>()
const CACHE_TTL = 60 * 60 * 1000 // 1 hour in milliseconds
export async function getOptionsIdsToLabels(locale: string = 'en') {
const cacheKey = `options-${locale}`
const now = Date.now()
const cached = cache.get(cacheKey)
if (cached && (now - cached.timestamp) < CACHE_TTL) {
return cached.data
}
// console.log("Fetching getOptionsIdsToLabels...")
const pg = createSupabaseDirectClient()
const result: Record<OptionTableKey, Record<string, string>> = {} as Record<OptionTableKey, Record<string, string>>
for (const tableKey of OPTION_TABLES) {
// const rows = await pg.manyOrNone(`
// SELECT
// id,
// COALESCE(
// (${tableKey}_translations.name) FILTER (WHERE ${tableKey}_translations.locale = $1),
// name
// ) as name
// FROM ${tableKey}
// LEFT JOIN ${tableKey}_translations ON ${tableKey}.id = ${tableKey}_translations.id
// ORDER BY name ASC
// `, [locale])
const rows = await pg.manyOrNone(`SELECT id, name
FROM ${tableKey}
ORDER BY name`, [locale])
const idToName: Record<string, string> = {}
rows.forEach(row => idToName[row.id] = row.name)
result[tableKey] = idToName
}
cache.set(cacheKey, {
data: result,
timestamp: Date.now()
})
// console.log({result})
return result
}

View File

@@ -18,6 +18,9 @@ const filterLabels: Record<string, string> = {
wants_kids_strength: "Kids",
is_smoker: "",
pref_relation_styles: "Seeking",
interests: "",
causes: "",
work: "",
religion: "",
pref_gender: "",
orderBy: "",
@@ -47,7 +50,11 @@ const skippedKeys = [
]
export function formatFilters(filters: Partial<FilterFields>, location: locationType | null): String[] | null {
export function formatFilters(
filters: Partial<FilterFields>,
location: locationType | null,
choicesIdsToLabels: Record<string, any>
): String[] | null {
const entries: String[] = []
let ageEntry = null
@@ -83,7 +90,12 @@ export function formatFilters(filters: Partial<FilterFields>, location: location
let stringValue = value
if (key === 'has_kids') stringValue = hasKidsNames[value as number]
if (key === 'wants_kids_strength') stringValue = wantsKidsNames[value as number]
if (Array.isArray(value)) stringValue = value.join(', ')
if (Array.isArray(value)) {
if (choicesIdsToLabels[key]) {
value = value.map((id) => choicesIdsToLabels[key][id])
}
stringValue = value.join(', ')
}
if (!label) {
const str = String(stringValue)

View File

@@ -13,6 +13,7 @@ import {useState} from "react";
import {useT} from "web/lib/locale";
import toast from "react-hot-toast";
import Link from "next/link";
import {useAllChoices} from "web/hooks/use-choices";
export function BookmarkSearchButton(props: {
bookmarkedSearches: BookmarkedSearchesType[]
@@ -70,6 +71,7 @@ function ButtonModal(props: {
}) {
const {open, setOpen, bookmarkedSearches, refreshBookmarkedSearches} = props
const t = useT()
const choicesIdsToLabels = useAllChoices()
return (
<Modal
open={open}
@@ -91,7 +93,7 @@ function ButtonModal(props: {
{(bookmarkedSearches || []).map((search) => (
<li key={search.id}
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
{formatFilters(search.search_filters as Partial<FilterFields>, search.location as locationType)?.join(" • ")}
{formatFilters(search.search_filters as Partial<FilterFields>, search.location as locationType, choicesIdsToLabels)?.join(" • ")}
<button
onClick={async () => {
await deleteBookmarkedSearch(search.id)

View File

@@ -3,8 +3,9 @@ import {run} from 'common/supabase/utils'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
import {db} from 'web/lib/supabase/db'
import {useLocale} from "web/lib/locale";
import {OptionTableKey} from "common/profiles/constants";
export async function fetchChoices(label: string, locale: string) {
export async function fetchChoices(label: OptionTableKey, locale: string) {
let choicesById: Record<string, string> = {};
const {data} = await run(
db
@@ -29,7 +30,7 @@ export async function fetchChoices(label: string, locale: string) {
return choicesById
}
export const useChoices = (label: string) => {
export const useChoices = (label: OptionTableKey) => {
const [choices, setChoices] = usePersistentInMemoryState<Record<string, string>>({}, `${label}-choices`)
const {locale} = useLocale()
@@ -50,3 +51,10 @@ export const useChoices = (label: string) => {
return {choices, refreshChoices}
}
export const useAllChoices = () => {
const {choices: interests, refreshChoices: refreshInterests} = useChoices('interests')
const {choices: causes, refreshChoices: refreshCauses} = useChoices('causes')
const {choices: work, refreshChoices: refreshWork} = useChoices('work')
return {interests, causes, work, refreshInterests, refreshCauses, refreshWork}
}