Clean home and fix profiles not loading

This commit is contained in:
MartinBraquet
2025-09-07 21:12:06 +02:00
parent d7c49fe19f
commit 5194b5f6bf
5 changed files with 203 additions and 265 deletions

View File

@@ -1,14 +0,0 @@
export function AboutBox(props: {
title: string
text: string
}) {
const {title, text} = props
return (
<div className="space-y-2">
<h3 className="text-lg font-bold">{title}</h3>
<p className="text-gray-600 dark:text-gray-400">
{text}
</p>
</div>
)
}

View File

@@ -0,0 +1,79 @@
import {useEffect} from "react";
import {Col} from "web/components/layout/col";
import {Button} from "web/components/buttons/button";
import {signupRedirect} from "web/lib/util/signup";
export function AboutBox(props: {
title: string
text: string
}) {
const {title, text} = props
return (
<div className="space-y-2">
<h3 className="text-lg font-bold">{title}</h3>
<p className="text-gray-600 dark:text-gray-400">
{text}
</p>
</div>
)
}
export function LoggedOutHome() {
useEffect(() => {
const text = "Search.";
const el = document.getElementById("typewriter");
if (!el) return;
let i = 0;
let timeoutId: any;
el.textContent = "";
function typeWriter() {
if (i < text.length && el) {
el.textContent = text.substring(0, i + 1);
i++;
timeoutId = setTimeout(typeWriter, 150);
}
}
const startId = setTimeout(typeWriter, 500);
return () => {
clearTimeout(timeoutId);
clearTimeout(startId);
if (el) el.textContent = text;
};
}, []);
return (
<>
<Col className="mb-4 gap-2 lg:hidden">
<Button
className="flex-1"
color="gradient"
size="xl"
onClick={signupRedirect}
>
Sign up
</Button>
{/*<SignUpAsMatchmaker className="flex-1"/>*/}
</Col>
<h1
className="pt-12 pb-2 text-7xl md:text-8xl xs:text-6xl font-extrabold max-w-4xl leading-tight xl:whitespace-nowrap md:whitespace-nowrap">
Don't Swipe.<br/>
<span id="typewriter"></span>
<span id="cursor" className="animate-pulse">|</span>
</h1>
<div className="w-full bg-gray-50 dark:bg-gray-900 py-8 mt-20">
<div className="max-w-6xl mx-auto px-4">
<div className="grid md:grid-cols-3 gap-8 text-center">
<AboutBox title="Radically Transparent" text="No algorithms. Every profile searchable."/>
<AboutBox title="Built for Depth" text="Filter by any keyword and what matters most."/>
<AboutBox title="Community Owned" text="Free forever. Built by users, for users."/>
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,119 @@
import {Lover} from 'common/love/lover'
import {removeNullOrUndefinedProps} from 'common/util/object'
import {Search} from 'web/components/filters/search'
import {useLover} from 'web/hooks/use-lover'
import {useCompatibleLovers} from 'web/hooks/use-lovers'
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'
import {useGetter} from 'web/hooks/use-getter'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
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'
export function ProfilesHome() {
const user = useUser();
const lover = useLover();
const you = lover;
const {
filters,
updateFilter,
clearFilters,
setYourFilters,
isYourFilters,
locationFilterProps,
} = useFilters(you ?? undefined);
const [lovers, setLovers] = usePersistentInMemoryState<Lover[] | undefined>(undefined, 'profile-lovers');
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [isReloading, setIsReloading] = useState(false);
const [debouncedAgeRange, setRawAgeRange] = useState({
min: filters.pref_age_min ?? PREF_AGE_MIN,
max: filters.pref_age_max ?? PREF_AGE_MAX,
});
const debouncedSetAge = useCallback(debounce((state) => setRawAgeRange(state), 50), []);
useEffect(() => {
if (!user) return;
debouncedSetAge({min: filters.pref_age_min ?? PREF_AGE_MIN, max: filters.pref_age_max ?? PREF_AGE_MAX});
}, [filters.pref_age_min, filters.pref_age_max]);
const id = useRef(0);
useEffect(() => {
if (!user) return;
setIsReloading(true);
const current = ++id.current;
api('get-lovers', removeNullOrUndefinedProps({limit: 20, compatibleWithUserId: user?.id, ...filters}) as any)
.then(({lovers}) => {
if (current === id.current) setLovers(lovers);
})
.finally(() => {
if (current === id.current) setIsReloading(false);
});
}, [JSON.stringify(omit(filters, ['pref_age_min', 'pref_age_max'])), debouncedAgeRange.min, debouncedAgeRange.max]);
const {data: starredUserIds, refresh: refreshStars} = useGetter('star', user?.id, getStars);
const compatibleLovers = useCompatibleLovers(user?.id);
const displayLovers = lovers && orderLovers(lovers, starredUserIds);
const loadMore = useCallback(async () => {
if (!lovers || isLoadingMore) return false;
try {
setIsLoadingMore(true);
const lastLover = lovers[lovers.length - 1];
const result = await api('get-lovers', removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
after: lastLover?.id.toString(), ...filters
}) as any);
if (result.lovers.length === 0) return false;
setLovers((prev) => (prev ? [...prev, ...result.lovers] : result.lovers));
return true;
} catch (err) {
console.error('Failed to load more lovers', err);
return false;
} finally {
setIsLoadingMore(false);
}
}, [lovers, filters, isLoadingMore, setLovers]);
return (
<>
{!lover && <Button className="mb-4 lg:hidden" onClick={() => Router.push('signup')}>Create a profile</Button>}
<Title className="!mb-2 text-3xl">Profiles</Title>
<Search
youLover={you}
starredUserIds={starredUserIds ?? []}
filters={filters}
updateFilter={updateFilter}
clearFilters={clearFilters}
setYourFilters={setYourFilters}
isYourFilters={isYourFilters}
locationFilterProps={locationFilterProps}
/>
{displayLovers === undefined || compatibleLovers === undefined ? (
<LoadingIndicator/>
) : (
<ProfileGrid
lovers={displayLovers}
loadMore={loadMore}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
compatibilityScores={compatibleLovers?.loverCompatibilityScores}
starredUserIds={starredUserIds}
refreshStars={refreshStars}
/>
)}
</>
);
}

View File

@@ -30,6 +30,7 @@ export const useCompatibleLovers = (
} else if (userId === null) setData(null)
}, [userId])
console.log('debug', data)
if (data && lover && options?.sortWithModifiers) {
data.compatibleLovers = sortBy(data.compatibleLovers, (l) => {
const modifier = !lover ? 1 : getLoversCompatibilityFactor(lover, l)

View File

@@ -1,265 +1,18 @@
import {Lover} from 'common/love/lover'
import {removeNullOrUndefinedProps} from 'common/util/object'
import {Search} from 'web/components/filters/search'
import {LovePage} from 'web/components/love-page'
import {useLover} from 'web/hooks/use-lover'
import {useCompatibleLovers} from 'web/hooks/use-lovers'
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 {Col} from 'web/components/layout/col'
import {ProfileGrid} from 'web/components/profile-grid'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {Title} from 'web/components/widgets/title'
import {useGetter} from 'web/hooks/use-getter'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
import {useSaveReferral} from 'web/hooks/use-save-referral'
import {useTracking} from 'web/hooks/use-tracking'
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 {signupRedirect} from "web/lib/util/signup";
import {AboutBox} from "web/components/about/box";
import {LoggedOutHome} from "web/components/home/home";
import {ProfilesHome} from "web/components/profiles/profiles-home";
export default function ProfilesPage() {
const you = useLover()
const {
filters,
updateFilter,
clearFilters,
setYourFilters,
isYourFilters,
locationFilterProps,
} = useFilters(you ?? undefined)
// Store all loaded lovers
const [lovers, setLovers] = usePersistentInMemoryState<Lover[] | undefined>(
undefined,
'profile-lovers'
)
const [isLoadingMore, setIsLoadingMore] = useState(false)
const [isReloading, setIsReloading] = useState(false)
// Refresh lovers when filters change
// debounce age filter
const [debouncedAgeRange, setRawAgeRange] = useState({
min: filters.pref_age_min ?? PREF_AGE_MIN,
max: filters.pref_age_max ?? PREF_AGE_MAX,
})
const debouncedSetAge = useCallback(
debounce(
(state: { min: number; max: number }) => setRawAgeRange(state),
50
),
[]
)
const user = useUser()
useEffect(() => {
if (!user) return
debouncedSetAge({
min: filters.pref_age_min ?? PREF_AGE_MIN,
max: filters.pref_age_max ?? PREF_AGE_MAX,
})
}, [filters.pref_age_min, filters.pref_age_max])
const id = useRef(0)
useEffect(() => {
if (!user) return
setIsReloading(true)
const current = ++id.current
api(
'get-lovers',
removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
...filters,
}) as any
)
.then(({lovers}) => {
if (current === id.current) {
setLovers(lovers)
}
})
.finally(() => {
if (current === id.current) {
setIsReloading(false)
}
})
}, [
JSON.stringify(omit(filters, ['pref_age_min', 'pref_age_max'])),
debouncedAgeRange.min,
debouncedAgeRange.max,
])
useTracking('view love profiles')
useSaveReferral(user)
const lover = useLover()
const {data: starredUserIds, refresh: refreshStars} = useGetter(
'star',
user?.id,
getStars
)
const compatibleLovers = useCompatibleLovers(user ? user.id : user)
const loadMore = useCallback(async () => {
if (!lovers || isLoadingMore) return false
try {
setIsLoadingMore(true)
// Get the last lover's ID as the after parameter
const lastLover = lovers[lovers.length - 1]
console.log('fetching lovers after', lastLover?.id)
const result = await api(
'get-lovers',
removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
after: lastLover?.id.toString(),
...filters,
} as any)
)
if (result.lovers.length === 0) {
return false
}
// Append new lovers to existing array
setLovers((prevLovers) => {
if (!prevLovers) return result.lovers
// Create a new array with all existing lovers plus new ones
return [...prevLovers, ...result.lovers]
})
return true
} catch (err) {
console.error('Failed to load more lovers', err)
return false
} finally {
setIsLoadingMore(false)
}
}, [lovers, filters, isLoadingMore, setLovers])
const displayLovers = lovers && orderLovers(lovers, starredUserIds)
useEffect(() => {
const text = "Search.";
const typewriter = document.getElementById("typewriter");
let i = 0;
let timeoutId: any;
if (typewriter) typewriter.textContent = ""
function typeWriter() {
if (i < text.length && typewriter) {
typewriter.textContent = text.substring(0, i + 1);
i++;
timeoutId = setTimeout(typeWriter, 150);
}
}
const intervalId = setTimeout(() => typeWriter(), 500);
return () => {
clearTimeout(timeoutId);
clearTimeout(intervalId);
if (typewriter) typewriter.textContent = "Search."
};
}, []);
// This makes document.getElementById("typewriter") null and prevents typeWriter from running.
// Should not be needed anyway.
// if (user === undefined) return <div/>
const user = useUser();
return (
<LovePage trackPageView={'user profiles'}>
<Col className="items-center">
<Col className={'bg-canvas-0 w-full rounded px-3 py-4 sm:px-6'}>
{user && lovers && !lover && (
<Button
className="mb-4 lg:hidden"
onClick={() => Router.push('signup')}
>
Create a profile
</Button>
)}
{user === null && (
<Col className="mb-4 gap-2 lg:hidden">
<Button
className="flex-1"
color="gradient"
size="xl"
onClick={signupRedirect}
>
Sign up
</Button>
{/*<SignUpAsMatchmaker className="flex-1"/>*/}
</Col>
)}
{!user && <>
<h1
className="pt-12 pb-2 text-7xl md:text-8xl xs:text-6xl font-extrabold max-w-4xl leading-tight xl:whitespace-nowrap md:whitespace-nowrap ">
Don't Swipe.<br/><span id="typewriter"></span><span id="cursor" className="animate-pulse">|</span>
</h1>
<div className="w-full bg-gray-50 dark:bg-gray-900 py-8 mt-20">
<div className="max-w-6xl mx-auto px-4">
<div className="grid md:grid-cols-3 gap-8 text-center">
<AboutBox
title='Radically Transparent'
text='No algorithms. Every profile searchable.'
/>
<AboutBox
title='Built for Depth'
text='Filter by any keyword and what matters most.'
/>
<AboutBox
title='Community Owned'
text='Free forever. Built by users, for users.'
/>
</div>
</div>
</div>
</>}
{user &&
<>
<Title className="!mb-2 text-3xl">Profiles</Title>
<Search
youLover={you}
starredUserIds={starredUserIds ?? []}
filters={filters}
updateFilter={updateFilter}
clearFilters={clearFilters}
setYourFilters={setYourFilters}
isYourFilters={isYourFilters}
locationFilterProps={locationFilterProps}
/>
{displayLovers === undefined || compatibleLovers === undefined ? (
<LoadingIndicator/>
) : (
<ProfileGrid
lovers={displayLovers}
loadMore={loadMore}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
compatibilityScores={compatibleLovers?.loverCompatibilityScores}
starredUserIds={starredUserIds}
refreshStars={refreshStars}
/>)
}
</>
}
{user ? <ProfilesHome/> : <LoggedOutHome/>}
</Col>
</Col>
</LovePage>