Add bookmarked search emails and factor out utils from web to common

This commit is contained in:
MartinBraquet
2025-09-16 12:36:18 +02:00
parent 31404cb89a
commit 639991dde4
27 changed files with 643 additions and 415 deletions

View File

@@ -57,7 +57,6 @@ export const API = (_apiTypeCheck = {
props: z.object({}),
returns: {} as {
status: 'success' | 'fail'
lovers: Lover[]
},
},
'mark-all-notifs-read': {

52
common/src/filters.ts Normal file
View File

@@ -0,0 +1,52 @@
import {Lover, LoverRow} from "common/love/lover";
import {cloneDeep} from "lodash";
import {filterDefined} from "common/util/array";
export type FilterFields = {
orderBy: 'last_online_time' | 'created_time' | 'compatibility_score'
geodbCityIds: string[] | null
genders: string[]
name: string | undefined
} & Pick<
LoverRow,
| 'wants_kids_strength'
| 'pref_relation_styles'
| 'is_smoker'
| 'has_kids'
| 'pref_gender'
| 'pref_age_min'
| 'pref_age_max'
>
export const orderLovers = (
lovers: Lover[],
starredUserIds: string[] | undefined
) => {
if (!lovers) return
let s = cloneDeep(lovers)
if (starredUserIds) {
s = filterDefined([
...starredUserIds.map((id) => s.find((l) => l.user_id === id)),
...s.filter((l) => !starredUserIds.includes(l.user_id)),
])
}
// s = alternateWomenAndMen(s)
return s
}
export const initialFilters: Partial<FilterFields> = {
geodbCityIds: undefined,
name: undefined,
genders: undefined,
pref_age_max: undefined,
pref_age_min: undefined,
has_kids: undefined,
wants_kids_strength: undefined,
is_smoker: undefined,
pref_relation_styles: undefined,
pref_gender: undefined,
orderBy: 'created_time',
}
export type OriginLocation = { id: string; name: string }

51
common/src/has-kids.ts Normal file
View File

@@ -0,0 +1,51 @@
export interface HasKidLabel {
name: string
shortName: string
value: number
}
export interface HasKidsLabelsMap {
[key: string]: HasKidLabel
}
export const hasKidsLabels: HasKidsLabelsMap = {
no_preference: {
name: 'Any kids',
shortName: 'Any kids',
value: -1,
},
has_kids: {
name: 'Has kids',
shortName: 'Yes',
value: 1,
},
doesnt_have_kids: {
name: `Doesn't have kids`,
shortName: 'No',
value: 0,
},
}
export const hasKidsNames = Object.values(hasKidsLabels).reduce<Record<number, string>>(
(acc, {value, name}) => {
acc[value] = name
return acc
},
{}
)
export const generateChoicesMap = (
labels: HasKidsLabelsMap
): Record<string, number> => {
return Object.values(labels).reduce(
(acc: Record<string, number>, label: HasKidLabel) => {
acc[label.shortName] = label.value
return acc
},
{}
)
}
// export const NO_PREFERENCE_STRENGTH = -1
// export const WANTS_KIDS_STRENGTH = 2
// export const DOESNT_WANT_KIDS_STRENGTH = 0

View File

@@ -0,0 +1,26 @@
export interface MatchPrivateUser {
email: string
notificationPreferences: any
}
export interface MatchUser {
name: string
username: string
}
export interface MatchesType {
description: {
filters: any; // You might want to replace 'any' with a more specific type
location: any; // You might want to replace 'any' with a more specific type
};
matches: any[]; // You might want to replace 'any' with a more specific type
id: string
}
export interface MatchesByUserType {
[key: string]: {
user: any;
privateUser: any;
matches: MatchesType[];
}
}

87
common/src/searches.ts Normal file
View File

@@ -0,0 +1,87 @@
// Define nice labels for each key
import {FilterFields, initialFilters} from "common/filters";
import {wantsKidsNames} from "common/wants-kids";
import {hasKidsNames} from "common/has-kids";
const filterLabels: Record<string, string> = {
geodbCityIds: "",
location: "",
name: "Searching",
genders: "",
pref_age_max: "Max age",
pref_age_min: "Min age",
has_kids: "",
wants_kids_strength: "Kids",
is_smoker: "",
pref_relation_styles: "Seeking",
pref_gender: "",
orderBy: "",
}
export type locationType = {
location: {
name: string
}
radius: number
}
export function formatFilters(filters: Partial<FilterFields>, location: locationType | null): String[] | null {
const entries: String[] = []
let ageEntry = null
let ageMin: number | undefined | null = filters.pref_age_min
if (ageMin == 18) ageMin = undefined
let ageMax = filters.pref_age_max;
if (ageMax == 99 || ageMax == 100) ageMax = undefined
if (ageMin || ageMax) {
let text: string = 'Age: '
if (ageMin) text = `${text}${ageMin}`
if (ageMax) {
if (ageMin) {
text = `${text}-${ageMax}`
} else {
text = `${text}up to ${ageMax}`
}
} else {
text = `${text}+`
}
ageEntry = text
}
Object.entries(filters).forEach(([key, value]) => {
const typedKey = key as keyof FilterFields
if (value === undefined || value === null) return
if (typedKey == 'pref_age_min' || typedKey == 'pref_age_max' || typedKey == 'geodbCityIds' || typedKey == 'orderBy') return
if (Array.isArray(value) && value.length === 0) return
if (initialFilters[typedKey] === value) return
const label = filterLabels[typedKey] ?? key
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 (!label) {
const str = String(stringValue)
stringValue = str.charAt(0).toUpperCase() + str.slice(1)
}
const display = stringValue
entries.push(`${label}${label ? ': ' : ''}${display}`)
})
if (ageEntry) entries.push(ageEntry)
if (location?.location?.name) {
const locString = `${location?.location?.name} (${location?.radius}mi)`
entries.push(locString)
}
if (entries.length === 0) return ['Anyone']
return entries
}

View File

@@ -8,6 +8,7 @@ export type notification_preferences = {
new_endorsement: notification_destination_types[]
new_love_like: notification_destination_types[]
new_love_ship: notification_destination_types[]
new_search_alerts: notification_destination_types[]
// User-related
new_message: notification_destination_types[]
@@ -37,6 +38,7 @@ export const getDefaultNotificationPreferences = (isDev?: boolean) => {
}
const defaults: notification_preferences = {
new_match: constructPref(true, true, true),
new_search_alerts: constructPref(true, true, true),
new_endorsement: constructPref(true, true, true),
new_love_like: constructPref(true, false, false),
new_love_ship: constructPref(true, false, false),
@@ -59,8 +61,10 @@ export const getNotificationDestinationsForUser = (
privateUser: PrivateUser,
type: notification_preference
) => {
const destinations = privateUser.notificationPreferences[type]
const opt_out = privateUser.notificationPreferences.opt_out_all
let destinations = privateUser.notificationPreferences[type]
if (!destinations) destinations = ['email', 'browser', 'mobile']
let opt_out = privateUser.notificationPreferences.opt_out_all
if (!opt_out) opt_out = []
return {
sendToEmail: destinations.includes('email') && !opt_out.includes('email'),

68
common/src/wants-kids.ts Normal file
View File

@@ -0,0 +1,68 @@
import {hasKidsLabels} from "common/has-kids";
export type KidLabel = {
name: string
shortName: string
strength: number
}
export type KidsLabelsMap = Record<string, KidLabel>
export const wantsKidsLabels: KidsLabelsMap = {
no_preference: {
name: 'Any preference',
shortName: 'Either',
strength: -1,
},
wants_kids: {
name: 'Wants kids',
shortName: 'Yes',
strength: 2,
},
doesnt_want_kids: {
name: `Doesn't want kids`,
shortName: 'No',
strength: 0,
},
}
export const wantsKidsNames = Object.values(wantsKidsLabels).reduce<Record<number, string>>(
(acc, {strength, name}) => {
acc[strength] = name
return acc
},
{}
)
export type wantsKidsDatabase = 0 | 1 | 2 | 3 | 4
export function wantsKidsToHasKidsFilter(wantsKidsStrength: wantsKidsDatabase) {
if (wantsKidsStrength < wantsKidsLabels.wants_kids.strength) {
return hasKidsLabels.doesnt_have_kids.value
}
return hasKidsLabels.no_preference.value
}
export function wantsKidsDatabaseToWantsKidsFilter(
wantsKidsStrength: wantsKidsDatabase
) {
// console.log(wantsKidsStrength)
if (wantsKidsStrength == wantsKidsLabels.no_preference.strength) {
return wantsKidsLabels.no_preference.strength
}
if (wantsKidsStrength > wantsKidsLabels.wants_kids.strength) {
return wantsKidsLabels.wants_kids.strength
}
if (wantsKidsStrength < wantsKidsLabels.wants_kids.strength) {
return wantsKidsLabels.doesnt_want_kids.strength
}
return wantsKidsLabels.no_preference.strength
}
export const generateChoicesMap = (labels: KidsLabelsMap): Record<string, number> => {
return Object.values(labels).reduce(
(acc: Record<string, number>, label: KidLabel) => {
acc[label.shortName] = label.strength
return acc
},
{}
)
}