diff --git a/web/components/filters/search.tsx b/web/components/filters/search.tsx
index 415d420..5b530df 100644
--- a/web/components/filters/search.tsx
+++ b/web/components/filters/search.tsx
@@ -1,5 +1,5 @@
import {Lover, LoverRow} from 'common/love/lover'
-import {useEffect, useState} from 'react'
+import React, {useEffect, useState} from 'react'
import {IoFilterSharp} from 'react-icons/io5'
import {Button} from 'web/components/buttons/button'
import {Col} from 'web/components/layout/col'
@@ -10,6 +10,12 @@ import {Select} from 'web/components/widgets/select'
import {DesktopFilters} from './desktop-filters'
import {LocationFilterProps} from './location-filter'
import {MobileFilters} from './mobile-filters'
+import {BookmarkSearchButton} from "web/components/searches/button";
+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";
export type FilterFields = {
orderBy: 'last_online_time' | 'created_time' | 'compatibility_score'
@@ -57,6 +63,8 @@ export const Search = (props: {
setYourFilters: (checked: boolean) => void
isYourFilters: boolean
locationFilterProps: LocationFilterProps
+ bookmarkedSearches: BookmarkedSearchesType[]
+ refreshBookmarkedSearches: () => void
}) => {
const {
youLover,
@@ -66,6 +74,8 @@ export const Search = (props: {
isYourFilters,
locationFilterProps,
filters,
+ bookmarkedSearches,
+ refreshBookmarkedSearches,
} = props
const [openFiltersModal, setOpenFiltersModal] = useState(false)
@@ -74,6 +84,10 @@ export const Search = (props: {
const [textToType, setTextToType] = useState(getRandomPair());
const [_, setCharIndex] = useState(0);
const [isHolding, setIsHolding] = useState(false);
+ const [bookmarked, setBookmarked] = useState(false);
+ const [loadingBookmark, setLoadingBookmark] = useState(false);
+ const [openBookmarks, setOpenBookmarks] = useState(false);
+ const user = useUser()
useEffect(() => {
if (isHolding) return;
@@ -100,8 +114,12 @@ export const Search = (props: {
return () => clearInterval(interval);
}, [textToType, isHolding]);
+ useEffect(() => {
+ setTimeout(() => setBookmarked(false), 2000);
+ }, [bookmarked]);
+
return (
-
+
+
+
+
+
+
)
}
diff --git a/web/components/filters/use-filters.ts b/web/components/filters/use-filters.ts
index 285da67..acfec24 100644
--- a/web/components/filters/use-filters.ts
+++ b/web/components/filters/use-filters.ts
@@ -39,7 +39,7 @@ export const orderLovers = (
// return filterDefined(zip(women, nonWomen).flat())
// }
-const initialFilters: Partial = {
+export const initialFilters: Partial = {
geodbCityIds: undefined,
name: undefined,
genders: undefined,
diff --git a/web/components/profile-grid.tsx b/web/components/profile-grid.tsx
index 0f78833..55d27c4 100644
--- a/web/components/profile-grid.tsx
+++ b/web/components/profile-grid.tsx
@@ -34,6 +34,8 @@ export const ProfileGrid = (props: {
const user = useUser()
+ const other_lovers = lovers.filter((lover) => lover.user_id !== user?.id);
+
return (
- {lovers
- .filter((lover) => lover.user_id !== user?.id)
+ {other_lovers
.map((lover) => (
-
- ))}
+
+ ))}
@@ -63,8 +64,11 @@ export const ProfileGrid = (props: {
)}
- {!isLoadingMore && !isReloading && lovers.length === 0 && (
- No profiles found
+ {!isLoadingMore && !isReloading && other_lovers.length === 0 && (
+
+
No profiles found.
+
Feel free to bookmark your search and we'll notify you when new users match it!
+
)}
)
@@ -76,8 +80,8 @@ function ProfilePreview(props: {
hasStar: boolean
refreshStars: () => Promise
}) {
- const {lover, compatibilityScore, hasStar, refreshStars} = props
- const {user, gender, age, pinned_url, city, bio} = lover
+ const {lover, compatibilityScore} = props
+ const {user} = lover
// const currentUser = useUser()
return (
diff --git a/web/components/profiles/profiles-home.tsx b/web/components/profiles/profiles-home.tsx
index 712356e..6c3db82 100644
--- a/web/components/profiles/profiles-home.tsx
+++ b/web/components/profiles/profiles-home.tsx
@@ -17,6 +17,7 @@ import {useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {debounce, omit} from 'lodash'
import {PREF_AGE_MAX, PREF_AGE_MIN,} from 'web/components/filters/location-filter'
+import {useBookmarkedSearches} from "web/hooks/use-bookmarked-searches";
export function ProfilesHome() {
const user = useUser();
@@ -33,6 +34,7 @@ export function ProfilesHome() {
} = useFilters(you ?? undefined);
const [lovers, setLovers] = usePersistentInMemoryState(undefined, 'profile-lovers');
+ const {bookmarkedSearches, refreshBookmarkedSearches} = useBookmarkedSearches(user?.id)
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [isReloading, setIsReloading] = useState(false);
@@ -105,6 +107,8 @@ export function ProfilesHome() {
setYourFilters={setYourFilters}
isYourFilters={isYourFilters}
locationFilterProps={locationFilterProps}
+ bookmarkedSearches={bookmarkedSearches}
+ refreshBookmarkedSearches={refreshBookmarkedSearches}
/>
{displayLovers === undefined || compatibleLovers === undefined ? (
diff --git a/web/components/searches/button.tsx b/web/components/searches/button.tsx
new file mode 100644
index 0000000..25feb70
--- /dev/null
+++ b/web/components/searches/button.tsx
@@ -0,0 +1,122 @@
+import {User} from "common/user";
+import {useEffect, useState} 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 {Row} from "web/components/layout/row";
+import {deleteBookmarkedSearch} from "web/lib/supabase/searches";
+import {FilterFields} from "web/components/filters/search";
+
+export function BookmarkSearchButton(props: {
+ bookmarkedSearches: BookmarkedSearchesType[]
+ refreshBookmarkedSearches: () => void
+ openBookmarks?: boolean
+}) {
+ const {
+ bookmarkedSearches,
+ refreshBookmarkedSearches,
+ openBookmarks,
+ } = props
+ const [open, setOpen] = useState(false)
+ const user = useUser()
+
+ useEffect(() => {
+ if (openBookmarks) setOpen(true)
+ }, [openBookmarks]);
+
+ if (!user) return null
+ return (
+ <>
+
+
+ >
+ )
+}
+
+function ButtonModal(props: {
+ open: boolean
+ setOpen: (open: boolean) => void
+ user: User
+ bookmarkedSearches: BookmarkedSearchesType[]
+ refreshBookmarkedSearches: () => void
+}) {
+ const {open, setOpen, bookmarkedSearches, refreshBookmarkedSearches} = props
+ return (
+ {
+ refreshBookmarkedSearches()
+ }}
+ >
+
+ Bookmarked Searches
+ We'll notify you daily when new people match your searches below.
+
+ {(bookmarkedSearches || []).map((search) => (
+
+
+ {JSON.stringify(
+ Object.fromEntries(
+ Object.entries(search.search_filters as Record).filter(([key, value]) => {
+ // skip null/undefined
+ if (value == null) return false
+
+ // skip empty arrays
+ if (Array.isArray(value) && value.length === 0) return false
+
+ // keep if different from initialFilters
+ return initialFilters[key as keyof FilterFields] !== value
+ })
+
+ )
+ )}
+
+
+
+
+ ))}
+
+ {/* {*/}
+ {/* setOpen(false)*/}
+ {/* }}*/}
+ {/* isLastQuestion={questionIndex === bookmarkedSearches.length - 1}*/}
+ {/* onNext={() => {*/}
+ {/* if (questionIndex === bookmarkedSearches.length - 1) {*/}
+ {/* setOpen(false)*/}
+ {/* } else {*/}
+ {/* setQuestionIndex(questionIndex + 1)*/}
+ {/* }*/}
+ {/* }}*/}
+ {/*/>*/}
+
+
+ )
+}
\ No newline at end of file
diff --git a/web/hooks/use-bookmarked-searches.ts b/web/hooks/use-bookmarked-searches.ts
new file mode 100644
index 0000000..c6c0fc2
--- /dev/null
+++ b/web/hooks/use-bookmarked-searches.ts
@@ -0,0 +1,25 @@
+import {usePersistentInMemoryState} from "web/hooks/use-persistent-in-memory-state";
+import {Row} from "common/supabase/utils";
+import {useEffect} from "react";
+import {getUserBookmarkedSearches} from "web/lib/supabase/searches";
+
+
+export const useBookmarkedSearches = (userId: string | undefined) => {
+ const [bookmarkedSearches, setBookmarkedSearches] = usePersistentInMemoryState[]>(
+ [],
+ `bookmarked-searches-${userId}`
+ )
+
+ useEffect(() => {
+ if (userId) getUserBookmarkedSearches(userId).then(setBookmarkedSearches)
+
+ }, [userId])
+
+ async function refreshBookmarkedSearches() {
+ if (userId) getUserBookmarkedSearches(userId).then(setBookmarkedSearches)
+ }
+
+ return {bookmarkedSearches, refreshBookmarkedSearches}
+}
+
+export type BookmarkedSearchesType = Row<'bookmarked_searches'>
\ No newline at end of file
diff --git a/web/lib/supabase/searches.ts b/web/lib/supabase/searches.ts
new file mode 100644
index 0000000..4e5c856
--- /dev/null
+++ b/web/lib/supabase/searches.ts
@@ -0,0 +1,51 @@
+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";
+
+
+export const getUserBookmarkedSearches = async (userId: string) => {
+ const {data} = await run(
+ db
+ .from('bookmarked_searches')
+ .select('*')
+ .eq('creator_id', userId)
+ .order('id', {ascending: false})
+ )
+ return data
+}
+
+export type BookmarkedSearchSubmitType = Omit<
+ Row<'bookmarked_searches'>,
+ 'created_time' | 'id' | 'last_notified_at'
+>
+
+export const submitBookmarkedSearch = async (
+ filters: Partial,
+ userId: string | undefined | null
+) => {
+ if (!filters) return
+ if (!userId) return
+ const row = {search_filters: filters, creator_id: userId}
+ const input = {
+ ...filterKeys(row, (key, _) => !['id', 'created_time', 'last_notified_at'].includes(key)),
+ } as BookmarkedSearchSubmitType
+
+ await run(
+ db.from('bookmarked_searches').upsert(input)
+ ).then(() => {
+ track('bookmarked_searches submit', {...filters})
+ })
+}
+
+export const deleteBookmarkedSearch = async (
+ id: number,
+) => {
+ if (!id) return
+ await run(
+ db.from('bookmarked_searches').delete().eq('id', id)
+ ).then(() => {
+ track('bookmarked_searches delete', {id})
+ })
+}
\ No newline at end of file