diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts
index 25e85ebc..3d6a2854 100644
--- a/backend/api/src/get-profiles.ts
+++ b/backend/api/src/get-profiles.ts
@@ -5,7 +5,6 @@ import {from, join, limit, orderBy, renderSql, select, where,} from 'shared/supa
import {getCompatibleLovers} from 'api/compatible-lovers'
import {intersection} from 'lodash'
import {MAX_INT, MIN_INT} from "common/constants";
-import {Lover} from "common/love/lover";
export type profileQueryType = {
limit?: number | undefined,
@@ -22,6 +21,7 @@ export type profileQueryType = {
is_smoker?: boolean | undefined,
geodbCityIds?: String[] | undefined,
compatibleWithUserId?: string | undefined,
+ skipId?: string | undefined,
orderBy?: string | undefined,
}
@@ -43,6 +43,7 @@ export const loadProfiles = async (props: profileQueryType) => {
geodbCityIds,
compatibleWithUserId,
orderBy: orderByParam,
+ skipId,
} = props
const keywords = name ? name.split(",").map(q => q.trim()).filter(Boolean) : []
@@ -50,7 +51,10 @@ export const loadProfiles = async (props: profileQueryType) => {
// compatibility. TODO: do this in sql
if (orderByParam === 'compatibility_score') {
- if (!compatibleWithUserId) return {status: 'fail', lovers: []}
+ if (!compatibleWithUserId) {
+ console.error('Incompatible with user ID')
+ throw Error('Incompatible with user ID')
+ }
const {compatibleLovers} = await getCompatibleLovers(compatibleWithUserId)
const lovers = compatibleLovers.filter(
@@ -72,6 +76,7 @@ export const loadProfiles = async (props: profileQueryType) => {
(has_kids == 0 && !l.has_kids) ||
(l.has_kids && l.has_kids > 0)) &&
(!is_smoker || l.is_smoker === is_smoker) &&
+ (l.id.toString() != skipId) &&
(!geodbCityIds ||
(l.geodb_city_id && geodbCityIds.includes(l.geodb_city_id)))
)
@@ -135,6 +140,8 @@ export const loadProfiles = async (props: profileQueryType) => {
geodbCityIds?.length &&
where(`geodb_city_id = ANY($(geodbCityIds))`, {geodbCityIds}),
+ skipId && where(`user_id != $(skipId)`, {skipId}),
+
orderBy(`${orderByParam} desc`),
after &&
where(
@@ -151,6 +158,10 @@ export const loadProfiles = async (props: profileQueryType) => {
}
export const getProfiles: APIHandler<'get-profiles'> = async (props, _auth) => {
- const lovers = await loadProfiles(props)
- return {status: 'success', lovers: lovers as Lover[]}
+ try {
+ const lovers = await loadProfiles(props)
+ return {status: 'success', lovers: lovers}
+ } catch {
+ return {status: 'fail', lovers: []}
+ }
}
diff --git a/backend/api/src/send-search-notifications.ts b/backend/api/src/send-search-notifications.ts
index af7e0012..7c6d8c62 100644
--- a/backend/api/src/send-search-notifications.ts
+++ b/backend/api/src/send-search-notifications.ts
@@ -1,12 +1,23 @@
import {APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from "shared/supabase/init";
-import {convertRow} from "shared/love/supabase";
-import {from, join, renderSql, select, where} from "shared/supabase/sql-builder";
+import {from, renderSql, select} from "shared/supabase/sql-builder";
+import {loadProfiles, profileQueryType} from "api/get-profiles";
+import {Row} from "common/supabase/utils";
+import {sendSearchAlertsEmail} from "email/functions/helpers";
+import {MatchesByUserType} from "common/love/bookmarked_searches";
+import {keyBy} from "lodash";
export function convertSearchRow(row: any): any {
return row
}
+
+export const notifyBookmarkedSearch = async (matches: MatchesByUserType) => {
+ for (const [_, value] of Object.entries(matches)) {
+ await sendSearchAlertsEmail(value.user, value.privateUser, value.matches)
+ }
+}
+
export const sendSearchNotifications: APIHandler<'send-search-notifications'> = async (_, auth) => {
const pg = createSupabaseDirectClient()
@@ -14,20 +25,58 @@ export const sendSearchNotifications: APIHandler<'send-search-notifications'> =
select('bookmarked_searches.*'),
from('bookmarked_searches'),
)
- const searches = pg.map(search_query, [], convertSearchRow)
+ const searches = await pg.map(search_query, [], convertSearchRow) as Row<'bookmarked_searches'>[]
+ console.log(`Running ${searches.length} bookmarked searches`)
- const query = renderSql(
- select('lovers.*, name, username, users.data as user'),
- from('lovers'),
- join('users on users.id = lovers.user_id'),
- where('looking_for_matches = true'),
- where(
- `(data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null)`
+ const _users = await pg.map(
+ renderSql(
+ select('users.*'),
+ from('users'),
),
- where(`data->>'userDeleted' != 'true' or data->>'userDeleted' is null`),
- )
+ [],
+ convertSearchRow
+ ) as Row<'users'>[]
+ const users = keyBy(_users, 'id')
+ console.log('users', users)
- const profiles = await pg.map(query, [], convertRow)
+ const _privateUsers = await pg.map(
+ renderSql(
+ select('private_users.*'),
+ from('private_users'),
+ ),
+ [],
+ convertSearchRow
+ ) as Row<'private_users'>[]
+ const privateUsers = keyBy(_privateUsers, 'id')
+ console.log('privateUsers', privateUsers)
- return {status: 'success', lovers: profiles}
+ const matches: MatchesByUserType = {}
+
+ for (const row of searches) {
+ if (typeof row.search_filters !== 'object') continue;
+ const props = {...row.search_filters, skipId: row.creator_id}
+ const profiles = await loadProfiles(props as profileQueryType)
+ console.log(profiles.map((item: any) => item.name))
+ if (!profiles.length) continue
+ if (!(row.creator_id in matches)) {
+ if (!privateUsers[row.creator_id]) continue
+ matches[row.creator_id] = {
+ user: users[row.creator_id],
+ privateUser: privateUsers[row.creator_id]['data'],
+ matches: [],
+ }
+ }
+ matches[row.creator_id].matches.push({
+ id: row.creator_id,
+ description: {filters: row.search_filters, location: row.location},
+ matches: [profiles.map((item: any) => ({
+ name: item.name,
+ username: item.username,
+ }))],
+ })
+ }
+ console.log(JSON.stringify(matches, null, 2))
+ await notifyBookmarkedSearch(matches)
+
+ return {status: 'success'}
}
\ No newline at end of file
diff --git a/backend/email/emails/functions/helpers.tsx b/backend/email/emails/functions/helpers.tsx
index e8b2102f..0a1c39de 100644
--- a/backend/email/emails/functions/helpers.tsx
+++ b/backend/email/emails/functions/helpers.tsx
@@ -1,12 +1,14 @@
-import { PrivateUser, User } from 'common/user'
-import { getNotificationDestinationsForUser } from 'common/user-notification-preferences'
-import { sendEmail } from './send-email'
-import { NewMatchEmail } from '../new-match'
-import { NewMessageEmail } from '../new-message'
-import { NewEndorsementEmail } from '../new-endorsement'
-import { Test } from '../test'
-import { getLover } from 'shared/love/supabase'
+import {PrivateUser, User} from 'common/user'
+import {getNotificationDestinationsForUser} from 'common/user-notification-preferences'
+import {sendEmail} from './send-email'
+import {NewMatchEmail} from '../new-match'
+import {NewMessageEmail} from '../new-message'
+import {NewEndorsementEmail} from '../new-endorsement'
+import {Test} from '../test'
+import {getLover} from 'shared/love/supabase'
import {renderToStaticMarkup} from "react-dom/server";
+import {MatchesType} from "common/love/bookmarked_searches";
+import NewSearchAlertsEmail from "email/new-search_alerts";
const from = 'Compass '
@@ -14,7 +16,7 @@ export const sendNewMatchEmail = async (
privateUser: PrivateUser,
matchedWithUser: User
) => {
- const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
'new_match'
)
@@ -44,7 +46,7 @@ export const sendNewMessageEmail = async (
toUser: User,
channelId: number
) => {
- const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
'new_message'
)
@@ -57,22 +59,6 @@ export const sendNewMessageEmail = async (
return
}
- console.log({
- from,
- subject: `${fromUser.name} sent you a message!`,
- to: privateUser.email,
- html: renderToStaticMarkup(
-
- ),
- })
-
return await sendEmail({
from,
subject: `${fromUser.name} sent you a message!`,
@@ -90,13 +76,40 @@ export const sendNewMessageEmail = async (
})
}
+export const sendSearchAlertsEmail = async (
+ toUser: User,
+ privateUser: PrivateUser,
+ matches: MatchesType[],
+) => {
+ const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
+ privateUser,
+ 'new_search_alerts'
+ )
+ const email = privateUser.email;
+ if (!email || !sendToEmail) return
+
+ return await sendEmail({
+ from,
+ subject: `Some people recently matched your bookmarked searches!`,
+ to: email,
+ html: renderToStaticMarkup(
+
+ ),
+ })
+}
+
export const sendNewEndorsementEmail = async (
privateUser: PrivateUser,
fromUser: User,
onUser: User,
text: string
) => {
- const { sendToEmail, unsubscribeUrl } = getNotificationDestinationsForUser(
+ const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
'new_endorsement'
)
@@ -123,6 +136,6 @@ export const sendTestEmail = async (toEmail: string) => {
from,
subject: 'Test email from Compass',
to: toEmail,
- html: renderToStaticMarkup( ),
+ html: renderToStaticMarkup( ),
})
}
diff --git a/backend/email/emails/new-search_alerts.tsx b/backend/email/emails/new-search_alerts.tsx
new file mode 100644
index 00000000..3e681f99
--- /dev/null
+++ b/backend/email/emails/new-search_alerts.tsx
@@ -0,0 +1,129 @@
+import {
+ Body,
+ Button,
+ Container,
+ Head,
+ Html,
+ Img,
+ Link,
+ Preview,
+ Section,
+ Text,
+} from '@react-email/components'
+import {type User} from 'common/user'
+import {type LoverRow} from 'common/love/lover'
+import {
+ jamesLover,
+ jamesUser,
+ sinclairLover,
+ sinclairUser,
+} from './functions/mock'
+import {DOMAIN} from 'common/envs/constants'
+import {getLoveOgImageUrl} from 'common/love/og-image'
+import {button, container, content, Footer, imageContainer, main, paragraph, profileImage} from "email/utils";
+import {sendSearchAlertsEmail} from "email/functions/helpers";
+import {MatchesType} from "common/love/bookmarked_searches";
+import {formatFilters, locationType} from "common/searches"
+import {FilterFields} from "common/filters";
+
+interface NewMessageEmailProps {
+ toUser: User
+ matches: MatchesType[]
+ unsubscribeUrl: string
+ email?: string
+}
+
+export const NewSearchAlertsEmail = ({
+ toUser,
+ unsubscribeUrl,
+ matches,
+ email,
+ }: NewMessageEmailProps) => {
+ const name = toUser.name.split(' ')[0]
+
+ return (
+
+
+ Some people recently matched your bookmarked searches!
+
+
+
+ Hi {name},
+ The following people matched your bookmarked searches in the past 24h:
+
+
+ {(matches || []).map((match) => (
+
+ {formatFilters(match.description.filters as Partial, match.description.location as locationType)}
+ {match.matches.map(p => p.toString()).join(', ')}
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+}
+
+const matchSamples = [
+ {
+ "id": "ID search 1",
+ "description": {
+ "filters": {
+ "orderBy": "created_time"
+ },
+ "location": null
+ },
+ "matches": [
+ [
+ {
+ "name": "James Bond",
+ "username": "jamesbond"
+ },
+ {
+ "name": "Lily",
+ "username": "lilyrose"
+ }
+ ]
+ ]
+ },
+ {
+ "id": "ID search 2",
+ "description": {
+ "filters": {
+ "genders": [
+ "female"
+ ],
+ "orderBy": "created_time"
+ },
+ "location": null
+ },
+ "matches": [
+ [
+ {
+ "name": "Lily",
+ "username": "lilyrose"
+ }
+ ]
+ ]
+ }
+]
+
+NewSearchAlertsEmail.PreviewProps = {
+ toUser: sinclairUser,
+ email: 'someone@gmail.com',
+ unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
+ matches: matchSamples,
+} as NewMessageEmailProps
+
+
+export default NewSearchAlertsEmail
diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts
index d2fa613f..080c2415 100644
--- a/common/src/api/schema.ts
+++ b/common/src/api/schema.ts
@@ -57,7 +57,6 @@ export const API = (_apiTypeCheck = {
props: z.object({}),
returns: {} as {
status: 'success' | 'fail'
- lovers: Lover[]
},
},
'mark-all-notifs-read': {
diff --git a/common/src/filters.ts b/common/src/filters.ts
new file mode 100644
index 00000000..f8070123
--- /dev/null
+++ b/common/src/filters.ts
@@ -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 = {
+ 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 }
diff --git a/common/src/has-kids.ts b/common/src/has-kids.ts
new file mode 100644
index 00000000..94dac020
--- /dev/null
+++ b/common/src/has-kids.ts
@@ -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>(
+ (acc, {value, name}) => {
+ acc[value] = name
+ return acc
+ },
+ {}
+)
+
+
+export const generateChoicesMap = (
+ labels: HasKidsLabelsMap
+): Record => {
+ return Object.values(labels).reduce(
+ (acc: Record, 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
\ No newline at end of file
diff --git a/common/src/love/bookmarked_searches.ts b/common/src/love/bookmarked_searches.ts
new file mode 100644
index 00000000..f6c14c9a
--- /dev/null
+++ b/common/src/love/bookmarked_searches.ts
@@ -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[];
+ }
+}
\ No newline at end of file
diff --git a/common/src/searches.ts b/common/src/searches.ts
new file mode 100644
index 00000000..5071d9d2
--- /dev/null
+++ b/common/src/searches.ts
@@ -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 = {
+ 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, 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
+}
\ No newline at end of file
diff --git a/common/src/user-notification-preferences.ts b/common/src/user-notification-preferences.ts
index 1cd1aa8d..18979ec1 100644
--- a/common/src/user-notification-preferences.ts
+++ b/common/src/user-notification-preferences.ts
@@ -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'),
diff --git a/common/src/wants-kids.ts b/common/src/wants-kids.ts
new file mode 100644
index 00000000..84ca86b0
--- /dev/null
+++ b/common/src/wants-kids.ts
@@ -0,0 +1,68 @@
+import {hasKidsLabels} from "common/has-kids";
+
+export type KidLabel = {
+ name: string
+ shortName: string
+ strength: number
+}
+
+export type KidsLabelsMap = Record
+
+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>(
+ (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 => {
+ return Object.values(labels).reduce(
+ (acc: Record, label: KidLabel) => {
+ acc[label.shortName] = label.strength
+ return acc
+ },
+ {}
+ )
+}
\ No newline at end of file
diff --git a/web/components/filters/age-filter.tsx b/web/components/filters/age-filter.tsx
index 9bd164f9..becb0ad4 100644
--- a/web/components/filters/age-filter.tsx
+++ b/web/components/filters/age-filter.tsx
@@ -1,6 +1,6 @@
import clsx from 'clsx'
-import { FilterFields } from './search'
import { RangeSlider } from 'web/components/widgets/slider'
+import {FilterFields} from "common/filters";
export const PREF_AGE_MIN = 18
export const PREF_AGE_MAX = 100
diff --git a/web/components/filters/desktop-filters.tsx b/web/components/filters/desktop-filters.tsx
index 55eb89d7..28c4146c 100644
--- a/web/components/filters/desktop-filters.tsx
+++ b/web/components/filters/desktop-filters.tsx
@@ -19,11 +19,12 @@ import {
RelationshipFilter,
RelationshipFilterText,
} from './relationship-filter'
-import { FilterFields } from './search'
-import { KidsLabel, wantsKidsLabels } from './wants-kids-filter'
-import { HasKidsLabel, hasKidsLabels } from './has-kids-filter'
+import { KidsLabel, wantsKidsLabelsWithIcon } from './wants-kids-filter'
+import { HasKidsLabel } from './has-kids-filter'
import { MyMatchesToggle } from './my-matches-toggle'
import { Lover } from 'common/love/lover'
+import {FilterFields} from "common/filters";
+import {hasKidsLabels} from "common/has-kids";
export function DesktopFilters(props: {
filters: Partial
diff --git a/web/components/filters/gender-filter.tsx b/web/components/filters/gender-filter.tsx
index 44cce16d..ef471959 100644
--- a/web/components/filters/gender-filter.tsx
+++ b/web/components/filters/gender-filter.tsx
@@ -3,7 +3,8 @@ import GenderIcon from '../gender-icon'
import { Gender } from 'common/gender'
import { Row } from 'web/components/layout/row'
import { MultiCheckbox } from 'web/components/multi-checkbox'
-import { FilterFields } from './search'
+
+import {FilterFields} from "common/filters";
export function GenderFilterText(props: {
gender: Gender[] | undefined
diff --git a/web/components/filters/has-kids-filter.tsx b/web/components/filters/has-kids-filter.tsx
index ddbc6286..2d006953 100644
--- a/web/components/filters/has-kids-filter.tsx
+++ b/web/components/filters/has-kids-filter.tsx
@@ -1,70 +1,20 @@
import clsx from 'clsx'
-import { Row } from 'web/components/layout/row'
-import { ChoicesToggleGroup } from 'web/components/widgets/choices-toggle-group'
-import { FilterFields } from './search'
-import { FaChild } from 'react-icons/fa6'
+import {Row} from 'web/components/layout/row'
+import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
+import {FaChild} from 'react-icons/fa6'
+import {FilterFields} from "common/filters";
+import {generateChoicesMap, hasKidsLabels} from "common/has-kids";
-export const NO_PREFERENCE_STRENGTH = -1
-export const WANTS_KIDS_STRENGTH = 2
-export const DOESNT_WANT_KIDS_STRENGTH = 0
-
-interface HasKidLabel {
- name: string
- shortName: string
- value: number
-}
-
-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>(
- (acc, { value, name }) => {
- acc[value] = name
- return acc
- },
- {}
-)
-
-const generateChoicesMap = (
- labels: HasKidsLabelsMap
-): Record => {
- return Object.values(labels).reduce(
- (acc: Record, label: HasKidLabel) => {
- acc[label.shortName] = label.value
- return acc
- },
- {}
- )
-}
export function HasKidsLabel(props: {
has_kids: number
highlightedClass?: string
mobile?: boolean
}) {
- const { has_kids, highlightedClass, mobile } = props
+ const {has_kids, highlightedClass, mobile} = props
return (
-
+
@@ -73,12 +23,12 @@ export function HasKidsLabel(props: {
? hasKidsLabels.has_kids.shortName
: hasKidsLabels.has_kids.name
: has_kids == hasKidsLabels.doesnt_have_kids.value
- ? mobile
- ? hasKidsLabels.doesnt_have_kids.shortName
- : hasKidsLabels.doesnt_have_kids.name
- : mobile
- ? hasKidsLabels.no_preference.shortName
- : hasKidsLabels.no_preference.name}
+ ? mobile
+ ? hasKidsLabels.doesnt_have_kids.shortName
+ : hasKidsLabels.doesnt_have_kids.name
+ : mobile
+ ? hasKidsLabels.no_preference.shortName
+ : hasKidsLabels.no_preference.name}
)
@@ -88,12 +38,12 @@ export function HasKidsFilter(props: {
filters: Partial
updateFilter: (newState: Partial) => void
}) {
- const { filters, updateFilter } = props
+ const {filters, updateFilter} = props
return (
updateFilter({ has_kids: Number(c) })}
+ setChoice={(c) => updateFilter({has_kids: Number(c)})}
toggleClassName="w-1/3 justify-center"
/>
)
diff --git a/web/components/filters/location-filter.tsx b/web/components/filters/location-filter.tsx
index 28b0069a..c3c1a817 100644
--- a/web/components/filters/location-filter.tsx
+++ b/web/components/filters/location-filter.tsx
@@ -1,26 +1,16 @@
import clsx from 'clsx'
-import { Col } from 'web/components/layout/col'
-import { Slider } from 'web/components/widgets/slider'
-import { usePersistentInMemoryState } from 'web/hooks/use-persistent-in-memory-state'
-import { Row } from 'web/components/layout/row'
-import {
- originToCity,
- City,
- CityRow,
- useCitySearch,
- loverToCity,
-} from '../search-location'
-import { Lover } from 'common/love/lover'
-import { useEffect, useState } from 'react'
-import { Input } from 'web/components/widgets/input'
-import { XIcon } from '@heroicons/react/solid'
-import { uniqBy } from 'lodash'
-import { buildArray } from 'common/util/array'
-
-export const PREF_AGE_MIN = 18
-export const PREF_AGE_MAX = 100
-
-export type OriginLocation = { id: string; name: string }
+import {Col} from 'web/components/layout/col'
+import {Slider} from 'web/components/widgets/slider'
+import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
+import {Row} from 'web/components/layout/row'
+import {City, CityRow, loverToCity, originToCity, useCitySearch,} from '../search-location'
+import {Lover} from 'common/love/lover'
+import {useEffect, useState} from 'react'
+import {Input} from 'web/components/widgets/input'
+import {XIcon} from '@heroicons/react/solid'
+import {uniqBy} from 'lodash'
+import {buildArray} from 'common/util/array'
+import {OriginLocation} from "common/filters";
export function LocationFilterText(props: {
location: OriginLocation | undefined | null
@@ -28,7 +18,7 @@ export function LocationFilterText(props: {
radius: number
highlightedClass?: string
}) {
- const { location, youLover, radius, highlightedClass } = props
+ const { location, radius, highlightedClass } = props
if (!location) {
return (
diff --git a/web/components/filters/mobile-filters.tsx b/web/components/filters/mobile-filters.tsx
index dfd506b0..9043a7e0 100644
--- a/web/components/filters/mobile-filters.tsx
+++ b/web/components/filters/mobile-filters.tsx
@@ -5,7 +5,7 @@ import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { AgeFilter, AgeFilterText, getNoMinMaxAge } from './age-filter'
import { GenderFilter, GenderFilterText } from './gender-filter'
-import { HasKidsFilter, HasKidsLabel, hasKidsLabels } from './has-kids-filter'
+import { HasKidsFilter, HasKidsLabel } from './has-kids-filter'
import {
LocationFilter,
LocationFilterProps,
@@ -16,18 +16,19 @@ import {
RelationshipFilter,
RelationshipFilterText,
} from './relationship-filter'
-import { FilterFields } from './search'
import {
KidsLabel,
WantsKidsFilter,
WantsKidsIcon,
- wantsKidsLabels,
+ wantsKidsLabelsWithIcon,
} from './wants-kids-filter'
import { FaChild } from 'react-icons/fa6'
import { MyMatchesToggle } from './my-matches-toggle'
import { Lover } from 'common/love/lover'
import { Gender } from 'common/gender'
import { RelationshipType } from 'web/lib/util/convert-relationship-type'
+import {FilterFields} from "common/filters";
+import {hasKidsLabels} from "common/has-kids";
export function MobileFilters(props: {
filters: Partial
diff --git a/web/components/filters/pref-gender-filter.tsx b/web/components/filters/pref-gender-filter.tsx
index 410fb034..0723375e 100644
--- a/web/components/filters/pref-gender-filter.tsx
+++ b/web/components/filters/pref-gender-filter.tsx
@@ -3,7 +3,8 @@ import GenderIcon from '../gender-icon'
import { Gender } from 'common/gender'
import { Row } from 'web/components/layout/row'
import { MultiCheckbox } from 'web/components/multi-checkbox'
-import { FilterFields } from './search'
+
+import {FilterFields} from "common/filters";
export function PrefGenderFilterText(props: {
pref_gender: Gender[] | undefined
diff --git a/web/components/filters/relationship-filter.tsx b/web/components/filters/relationship-filter.tsx
index 77231f12..18af5ac9 100644
--- a/web/components/filters/relationship-filter.tsx
+++ b/web/components/filters/relationship-filter.tsx
@@ -1,10 +1,10 @@
import clsx from 'clsx'
import {convertRelationshipType, RelationshipType,} from 'web/lib/util/convert-relationship-type'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
-import {FilterFields} from './search'
import {MultiCheckbox} from 'web/components/multi-checkbox'
import {RELATIONSHIP_CHOICES} from "web/components/filters/choices";
+import {FilterFields} from "common/filters";
export function RelationshipFilterText(props: {
relationship: RelationshipType[] | undefined
diff --git a/web/components/filters/search.tsx b/web/components/filters/search.tsx
index 2c840a8b..c7c610e8 100644
--- a/web/components/filters/search.tsx
+++ b/web/components/filters/search.tsx
@@ -1,4 +1,4 @@
-import {Lover, LoverRow} from 'common/love/lover'
+import {Lover} from 'common/love/lover'
import React, {useEffect, useState} from 'react'
import {IoFilterSharp} from 'react-icons/io5'
import {Button} from 'web/components/buttons/button'
@@ -15,24 +15,8 @@ import {BookmarkedSearchesType} from "web/hooks/use-bookmarked-searches";
import {submitBookmarkedSearch} from "web/lib/supabase/searches";
import {useUser} from "web/hooks/use-user";
import {isEqual} from "lodash";
-import {initialFilters} from "web/components/filters/use-filters";
import toast from "react-hot-toast";
-
-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'
->
+import {FilterFields, initialFilters} from "common/filters";
function isOrderBy(input: string): input is FilterFields['orderBy'] {
return ['last_online_time', 'created_time', 'compatibility_score'].includes(
diff --git a/web/components/filters/use-filters.ts b/web/components/filters/use-filters.ts
index 6303ad29..9a144b07 100644
--- a/web/components/filters/use-filters.ts
+++ b/web/components/filters/use-filters.ts
@@ -1,88 +1,43 @@
-import { Lover } from 'common/love/lover'
-import { filterDefined } from 'common/util/array'
-import { cloneDeep, debounce, isEqual } from 'lodash'
-import { useCallback } from 'react'
-import { useEffectCheckEquality } from 'web/hooks/use-effect-check-equality'
-import { useIsLooking } from 'web/hooks/use-is-looking'
-import { useNearbyCities } from 'web/hooks/use-nearby-locations'
-import { usePersistentLocalState } from 'web/hooks/use-persistent-local-state'
-import { OriginLocation } from './location-filter'
-import { FilterFields } from './search'
-import {
- wantsKidsDatabaseToWantsKidsFilter,
- wantsKidsDatabase,
- wantsKidsToHasKidsFilter,
-} from './wants-kids-filter'
-
-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
-}
-
-// const alternateWomenAndMen = (lovers: Lover[]) => {
-// const [women, nonWomen] = partition(lovers, (l) => l.gender === 'female')
-// return filterDefined(zip(women, nonWomen).flat())
-// }
-
-export const initialFilters: Partial = {
- 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',
-}
+import {Lover} from "common/love/lover";
+import {useIsLooking} from "web/hooks/use-is-looking";
+import {usePersistentLocalState} from "web/hooks/use-persistent-local-state";
+import {useCallback} from "react";
+import {debounce, isEqual} from "lodash";
+import {useNearbyCities} from "web/hooks/use-nearby-locations";
+import {useEffectCheckEquality} from "web/hooks/use-effect-check-equality";
+import {wantsKidsDatabase, wantsKidsDatabaseToWantsKidsFilter, wantsKidsToHasKidsFilter} from "common/wants-kids";
+import {FilterFields, initialFilters, OriginLocation} from "common/filters";
export const useFilters = (you: Lover | undefined) => {
const isLooking = useIsLooking()
const [filters, setFilters] = usePersistentLocalState>(
- isLooking ? initialFilters : { ...initialFilters, orderBy: 'created_time' },
+ isLooking ? initialFilters : {...initialFilters, orderBy: 'created_time'},
'profile-filters-2'
)
const updateFilter = (newState: Partial) => {
- const updatedState = { ...newState }
-
+ const updatedState = {...newState}
+
if ('pref_age_min' in updatedState && updatedState.pref_age_min !== undefined) {
if (updatedState.pref_age_min != null && updatedState.pref_age_min <= 18) {
updatedState.pref_age_min = undefined
}
}
-
+
if ('pref_age_max' in updatedState && updatedState.pref_age_max !== undefined) {
if (updatedState.pref_age_max != null && updatedState.pref_age_max >= 99) {
updatedState.pref_age_max = undefined
}
}
-
- setFilters((prevState) => ({ ...prevState, ...updatedState }))
+
+ setFilters((prevState) => ({...prevState, ...updatedState}))
}
const clearFilters = () => {
setFilters(
isLooking
? initialFilters
- : { ...initialFilters, orderBy: 'created_time' }
+ : {...initialFilters, orderBy: 'created_time'}
)
setLocation(undefined)
}
@@ -101,7 +56,7 @@ export const useFilters = (you: Lover | undefined) => {
const nearbyCities = useNearbyCities(location?.id, radius)
useEffectCheckEquality(() => {
- updateFilter({ geodbCityIds: nearbyCities })
+ updateFilter({geodbCityIds: nearbyCities})
}, [nearbyCities])
const locationFilterProps = {
@@ -144,7 +99,7 @@ export const useFilters = (you: Lover | undefined) => {
setRadius(100)
debouncedSetRadius(100) // clear any pending debounced sets
if (you?.geodb_city_id && you.city) {
- setLocation({ id: you?.geodb_city_id, name: you?.city })
+ setLocation({id: you?.geodb_city_id, name: you?.city})
}
} else {
clearFilters()
@@ -160,3 +115,8 @@ export const useFilters = (you: Lover | undefined) => {
locationFilterProps,
}
}
+
+// const alternateWomenAndMen = (lovers: Lover[]) => {
+// const [women, nonWomen] = partition(lovers, (l) => l.gender === 'female')
+// return filterDefined(zip(women, nonWomen).flat())
+// }
\ No newline at end of file
diff --git a/web/components/filters/wants-kids-filter.tsx b/web/components/filters/wants-kids-filter.tsx
index 3d2e252e..0a36de1c 100644
--- a/web/components/filters/wants-kids-filter.tsx
+++ b/web/components/filters/wants-kids-filter.tsx
@@ -2,94 +2,45 @@ import {ReactNode} from 'react'
import {MdNoStroller, MdOutlineStroller, MdStroller} from 'react-icons/md'
import {Row} from 'web/components/layout/row'
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
-import {FilterFields} from './search'
-import {hasKidsLabels} from './has-kids-filter'
import clsx from 'clsx'
+import {FilterFields} from "common/filters";
-interface KidLabel {
- name: string
- shortName: string
+import {generateChoicesMap, KidLabel, wantsKidsLabels} from "common/wants-kids"
+
+interface KidLabelWithIcon extends KidLabel {
icon: ReactNode
- strength: number
}
-interface KidsLabelsMap {
- [key: string]: KidLabel
+interface KidsLabelsMapWithIcon {
+ [key: string]: KidLabelWithIcon
}
-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 wantsKidsLabels: KidsLabelsMap = {
+export const wantsKidsLabelsWithIcon: KidsLabelsMapWithIcon = {
+ ...wantsKidsLabels,
no_preference: {
- name: 'Any preference',
- shortName: 'Either',
+ ...wantsKidsLabels.no_preference,
icon: ,
- strength: -1,
},
wants_kids: {
- name: 'Wants kids',
- shortName: 'Yes',
+ ...wantsKidsLabels.wants_kids,
icon: ,
- strength: 2,
},
doesnt_want_kids: {
- name: `Doesn't want kids`,
- shortName: 'No',
+ ...wantsKidsLabels.doesnt_want_kids,
icon: ,
- strength: 0,
},
}
-export const wantsKidsNames = Object.values(wantsKidsLabels).reduce>(
- (acc, {strength, name}) => {
- acc[strength] = name
- return acc
- },
- {}
-)
-
-const generateChoicesMap = (labels: KidsLabelsMap): Record => {
- return Object.values(labels).reduce(
- (acc: Record, label: KidLabel) => {
- acc[label.shortName] = label.strength
- return acc
- },
- {}
- )
-}
export function WantsKidsIcon(props: { strength: number; className?: string }) {
const {strength, className} = props
return (
- {strength == wantsKidsLabels.no_preference.strength
- ? wantsKidsLabels.no_preference.icon
- : strength == wantsKidsLabels.wants_kids.strength
- ? wantsKidsLabels.wants_kids.icon
- : wantsKidsLabels.doesnt_want_kids.icon}
+ {strength == wantsKidsLabelsWithIcon.no_preference.strength
+ ? wantsKidsLabelsWithIcon.no_preference.icon
+ : strength == wantsKidsLabelsWithIcon.wants_kids.strength
+ ? wantsKidsLabelsWithIcon.wants_kids.icon
+ : wantsKidsLabelsWithIcon.doesnt_want_kids.icon}
)
}
@@ -106,21 +57,21 @@ export function KidsLabel(props: {
- {strength == wantsKidsLabels.no_preference.strength
+ {strength == wantsKidsLabelsWithIcon.no_preference.strength
? mobile
- ? wantsKidsLabels.no_preference.shortName
- : wantsKidsLabels.no_preference.name
- : strength == wantsKidsLabels.wants_kids.strength
+ ? wantsKidsLabelsWithIcon.no_preference.shortName
+ : wantsKidsLabelsWithIcon.no_preference.name
+ : strength == wantsKidsLabelsWithIcon.wants_kids.strength
? mobile
- ? wantsKidsLabels.wants_kids.shortName
- : wantsKidsLabels.wants_kids.name
+ ? wantsKidsLabelsWithIcon.wants_kids.shortName
+ : wantsKidsLabelsWithIcon.wants_kids.name
: mobile
- ? wantsKidsLabels.doesnt_want_kids.shortName
- : wantsKidsLabels.doesnt_want_kids.name}
+ ? wantsKidsLabelsWithIcon.doesnt_want_kids.shortName
+ : wantsKidsLabelsWithIcon.doesnt_want_kids.name}
)
@@ -135,7 +86,7 @@ export function WantsKidsFilter(props: {
return (
updateFilter({wants_kids_strength: Number(c)})}
toggleClassName="w-1/3 justify-center"
/>
diff --git a/web/components/profiles/profiles-home.tsx b/web/components/profiles/profiles-home.tsx
index 0aa9a9e0..6d7f4e9b 100644
--- a/web/components/profiles/profiles-home.tsx
+++ b/web/components/profiles/profiles-home.tsx
@@ -7,7 +7,6 @@ import {getStars} from 'web/lib/supabase/stars'
import Router from 'next/router'
import {useCallback, useEffect, useRef, useState} from 'react'
import {Button} from 'web/components/buttons/button'
-import {orderLovers, useFilters} from 'web/components/filters/use-filters'
import {ProfileGrid} from 'web/components/profile-grid'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {Title} from 'web/components/widgets/title'
@@ -16,6 +15,8 @@ import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-sta
import {useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {useBookmarkedSearches} from "web/hooks/use-bookmarked-searches";
+import {orderLovers} from "common/filters";
+import {useFilters} from "web/components/filters/use-filters";
export function ProfilesHome() {
const user = useUser();
diff --git a/web/components/search-location.tsx b/web/components/search-location.tsx
index 6612b445..daade569 100644
--- a/web/components/search-location.tsx
+++ b/web/components/search-location.tsx
@@ -1,9 +1,9 @@
import clsx from 'clsx'
import { useEffect, useRef, useState } from 'react'
-import { OriginLocation } from './filters/location-filter'
import { api } from 'web/lib/api'
import { countryCodeToFlag } from 'web/lib/util/location'
import { LoverRow } from 'common/love/lover'
+import {OriginLocation} from "common/filters";
export type City = {
geodb_city_id: string
diff --git a/web/components/searches/button.tsx b/web/components/searches/button.tsx
index f8c8eacf..b43230bf 100644
--- a/web/components/searches/button.tsx
+++ b/web/components/searches/button.tsx
@@ -1,15 +1,12 @@
import {User} from "common/user";
-import {ReactElement} from "react";
import {Button} from "web/components/buttons/button";
import {Modal, MODAL_CLASS} from "web/components/layout/modal";
import {Col} from "web/components/layout/col";
import {BookmarkedSearchesType} from "web/hooks/use-bookmarked-searches";
import {useUser} from "web/hooks/use-user";
-import {initialFilters} from "web/components/filters/use-filters";
import {deleteBookmarkedSearch} from "web/lib/supabase/searches";
-import {FilterFields} from "web/components/filters/search";
-import {hasKidsNames} from "web/components/filters/has-kids-filter";
-import {wantsKidsNames} from "web/components/filters/wants-kids-filter";
+import {formatFilters, locationType} from "common/searches";
+import {FilterFields} from "common/filters";
export function BookmarkSearchButton(props: {
bookmarkedSearches: BookmarkedSearchesType[]
@@ -42,107 +39,6 @@ export function BookmarkSearchButton(props: {
)
}
-// Define nice labels for each key
-const filterLabels: Record = {
- 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
-}
-
-
-function formatFilters(filters: Partial, location: locationType | null): ReactElement | null {
- const entries: ReactElement[] = []
-
- 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: ReactElement = key === 'name'
- ? {stringValue as string}
- : <>{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 Anything
-
- // Join with " • " separators
- return (
-
- {entries.map((entry, i) => (
-
- {entry}
- {i < entries.length - 1 ? ' • ' : ''}
-
- ))}
-
- )
-}
function ButtonModal(props: {
open: boolean
@@ -172,7 +68,7 @@ function ButtonModal(props: {
{(bookmarkedSearches || []).map((search) => (
- {formatFilters(search.search_filters as Partial, search.location as locationType)}
+ {formatFilters(search.search_filters as Partial, search.location as locationType)?.join(" • ")}
{
await deleteBookmarkedSearch(search.id)
diff --git a/web/lib/supabase/searches.ts b/web/lib/supabase/searches.ts
index 6b062cbf..d6157d5d 100644
--- a/web/lib/supabase/searches.ts
+++ b/web/lib/supabase/searches.ts
@@ -2,8 +2,8 @@ import {Row, run} from "common/supabase/utils";
import {db} from "web/lib/supabase/db";
import {filterKeys} from "web/components/questions-form";
import {track} from "web/lib/service/analytics";
-import {FilterFields} from "web/components/filters/search";
import {removeNullOrUndefinedProps} from "common/util/object";
+import {FilterFields} from "common/filters";
export const getUserBookmarkedSearches = async (userId: string) => {
diff --git a/web/pages/notifications.tsx b/web/pages/notifications.tsx
index 9c38be86..6e44c581 100644
--- a/web/pages/notifications.tsx
+++ b/web/pages/notifications.tsx
@@ -159,7 +159,7 @@ const LoadedNotificationSettings = (props: { privateUser: PrivateUser }) => {
},
{
type: 'new_message',
- question: '... sends you a new messages?',
+ question: '... sends you a new message?',
},
{
type: 'new_love_like',
@@ -181,7 +181,10 @@ const LoadedNotificationSettings = (props: { privateUser: PrivateUser }) => {
type: 'on_new_follow',
question: '... follows you?',
},
-
+ {
+ type: 'new_search_alerts',
+ question: 'Alerts from bookmarked searches?',
+ },
{
type: 'opt_out_all',
question: