diff --git a/app/api/profiles/route.ts b/app/api/profiles/route.ts
index 31780747..582aead6 100644
--- a/app/api/profiles/route.ts
+++ b/app/api/profiles/route.ts
@@ -1,22 +1,84 @@
-import { prisma }from "@/lib/server/prisma";
+import { prisma } from "@/lib/server/prisma";
import { NextResponse } from "next/server";
-import {getSession} from "@/lib/server/auth";
+import { getSession } from "@/lib/server/auth";
+
+type FilterParams = {
+ gender?: string;
+ interests?: string[];
+ causeAreas?: string[];
+ searchQuery?: string;
+};
export async function GET(request: Request) {
const url = new URL(request.url);
const page = parseInt(url.searchParams.get("page") || "1");
+ const gender = url.searchParams.get("gender");
+ const interests = url.searchParams.get("interests")?.split(",").filter(Boolean) || [];
+ const causeAreas = url.searchParams.get("causeAreas")?.split(",").filter(Boolean) || [];
+ const searchQuery = url.searchParams.get("search") || "";
+
const profilesPerPage = 20;
const offset = (page - 1) * profilesPerPage;
const session = await getSession();
console.log(`Session: ${session?.user?.name}`);
- // Fetch paginated posts
+ // Build the where clause based on filters
+ const where: any = {
+ id: { not: session?.user?.id },
+ };
+
+ if (gender) {
+ where.profile = {
+ ...where.profile,
+ gender: gender,
+ };
+ }
+
+ if (interests.length > 0) {
+ where.profile = {
+ ...where.profile,
+ intellectualInterests: {
+ some: {
+ interest: {
+ name: { in: interests },
+ },
+ },
+ },
+ };
+ }
+
+ if (causeAreas.length > 0) {
+ where.profile = {
+ ...where.profile,
+ causeAreas: {
+ some: {
+ causeArea: {
+ name: { in: causeAreas },
+ },
+ },
+ },
+ };
+ }
+
+ if (searchQuery) {
+ where.OR = [
+ { name: { contains: searchQuery, mode: 'insensitive' } },
+ { email: { contains: searchQuery, mode: 'insensitive' } },
+ {
+ profile: {
+ description: { contains: searchQuery, mode: 'insensitive' },
+ },
+ },
+ ];
+ }
+
+ // Fetch paginated and filtered profiles
const profiles = await prisma.user.findMany({
skip: offset,
take: profilesPerPage,
orderBy: { createdAt: "desc" },
- where: { id: {not: session?.user?.id} },
+ where,
select: {
id: true,
name: true,
@@ -31,27 +93,6 @@ export async function GET(request: Request) {
},
},
},
- // where: {
- // id: {
- // not: session?.user?.id, // Exclude the logged-in user
- // gender: 'FEMALE',
- // intellectualInterests: {
- // some: {
- // interest: { name: 'Philosophy' }
- // }
- // },
- // causeAreas: {
- // some: {
- // causeArea: { name: 'AI Safety' }
- // }
- // }
- // },
- // include: {
- // user: true,
- // intellectualInterests: { include: { interest: true } },
- // causeAreas: { include: { causeArea: true } }
- // }
- // },
});
const totalProfiles = await prisma.user.count();
diff --git a/app/profiles/ProfileFilters.tsx b/app/profiles/ProfileFilters.tsx
new file mode 100644
index 00000000..84b89fea
--- /dev/null
+++ b/app/profiles/ProfileFilters.tsx
@@ -0,0 +1,147 @@
+'use client';
+
+import { useState } from 'react';
+
+// Mock data for filter options
+const GENDER_OPTIONS = [
+ { value: 'Male', label: 'Male' },
+ { value: 'Female', label: 'Female' },
+ { value: 'NonBinary', label: 'Non-binary' },
+ { value: 'Other', label: 'Prefer not to say' },
+];
+
+// These would ideally come from your database
+const INTEREST_OPTIONS = [
+ 'AI Safety',
+ 'Philosophy',
+ 'Effective Altruism',
+ 'Rationality',
+ 'Technology',
+ 'Science',
+ 'Policy',
+];
+
+const CAUSE_AREA_OPTIONS = [
+ 'AI Safety',
+ 'Global Health',
+ 'Animal Welfare',
+ 'Biosecurity',
+ 'Climate Change',
+ 'Global Poverty',
+];
+
+interface FilterProps {
+ filters: {
+ gender: string;
+ interests: string[];
+ causeAreas: string[];
+ searchQuery: string;
+ };
+ onFilterChange: (key: string, value: any) => void;
+ onToggleFilter: (key: 'interests' | 'causeAreas', value: string) => void;
+ onReset: () => void;
+}
+
+export function ProfileFilters({ filters, onFilterChange, onToggleFilter, onReset }: FilterProps) {
+ const [showFilters, setShowFilters] = useState(false);
+
+ return (
+
+
+
+
onFilterChange('searchQuery', e.target.value)}
+ />
+
+
+
+
+
+
+ {showFilters && (
+
+
+
+
+
+
+
+
+
+
+ {INTEREST_OPTIONS.map((interest) => (
+
+ ))}
+
+
+
+
+
+
+ {CAUSE_AREA_OPTIONS.map((cause) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/app/profiles/page.tsx b/app/profiles/page.tsx
index f1c0a20f..b8baca6f 100644
--- a/app/profiles/page.tsx
+++ b/app/profiles/page.tsx
@@ -1,67 +1,177 @@
'use client';
import Link from "next/link";
-import {useEffect, useState} from "react";
+import {useCallback, useEffect, useState} from "react";
+import {useDebounce} from "use-debounce";
import LoadingSpinner from "@/lib/client/LoadingSpinner";
import {ProfileData} from "@/lib/client/schema";
-
+import {ProfileFilters} from "./ProfileFilters";
// Disable static generation
export const dynamic = "force-dynamic";
-
export default function ProfilePage() {
-
const [profiles, setProfiles] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [filters, setFilters] = useState({
+ gender: '',
+ interests: [] as string[],
+ causeAreas: [] as string[],
+ searchQuery: '',
+ });
+ const [debouncedFilters] = useDebounce(filters, 500);
+ const fetchProfiles = useCallback(async () => {
+ try {
+ setLoading(true);
+ const params = new URLSearchParams();
+
+ if (filters.gender) params.append('gender', filters.gender);
+ if (filters.interests.length > 0) params.append('interests', filters.interests.join(','));
+ if (filters.causeAreas.length > 0) params.append('causeAreas', filters.causeAreas.join(','));
+ if (filters.searchQuery) params.append('search', filters.searchQuery);
+
+ const response = await fetch(`/api/profiles?${params.toString()}`);
+ const data = await response.json();
+
+ if (!response.ok) {
+ console.error(data.error || 'Failed to fetch profiles');
+ return;
+ }
+
+ setProfiles(data.profiles || []);
+ } catch (error) {
+ console.error('Error fetching profiles:', error);
+ } finally {
+ setLoading(false);
+ }
+ }, [filters]);
useEffect(() => {
- const fetchProfile = async () => {
- try {
- const response = await fetch('/api/profiles');
- console.log(response)
+ fetchProfiles();
+ }, [fetchProfiles]);
- const data = await response.json();
- console.log(data)
+ const handleFilterChange = (key: string, value: any) => {
+ setFilters(prev => ({
+ ...prev,
+ [key]: value
+ }));
+ };
- if (!response.ok) {
- console.error(data.error || 'Failure');
- }
+ const toggleFilter = (key: 'interests' | 'causeAreas', value: string) => {
+ setFilters(prev => ({
+ ...prev,
+ [key]: prev[key].includes(value)
+ ? prev[key].filter((item: string) => item !== value)
+ : [...prev[key], value]
+ }));
+ };
- const p = data['profiles'];
- setProfiles(p);
- } catch (error) {
- console.error('Upload error:', error);
- }
- };
-
- fetchProfile();
- }, []);
-
- if (!profiles) return ;
+ const resetFilters = () => {
+ setFilters({
+ gender: '',
+ interests: [],
+ causeAreas: [],
+ searchQuery: '',
+ });
+ };
return (
-
-
Profiles
-
- {profiles.length > 0 ?
- (profiles.map((user) => (
-
-
-
{user.name}
- {user?.profile?.description && (
-
-
{user.profile.description}
-
- )}
-
-
- ))) : (
-
- There are no profiles for this search. Relax the filters or come back later.
+
+
+
Profiles
+
+
+
+ {loading ? (
+
+
- )
- }
+ ) : (
+
+ {profiles.length > 0 ? (
+ profiles.map((user) => (
+
+
+
+
+
+ {user.name}
+
+ {user?.profile?.description && (
+
+ {user.profile.description}
+
+ )}
+
+
+ {user.profile?.intellectualInterests && user.profile.intellectualInterests.length > 0 && (
+
+ {user.profile.intellectualInterests.slice(0, 3).map(({interest}) => (
+
+ {interest?.name}
+
+ ))}
+
+ )}
+ {user.profile?.causeAreas && user.profile.causeAreas.length > 0 && (
+
+ {user.profile.causeAreas.slice(0, 3).map(({causeArea}) => (
+
+ {causeArea?.name}
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ ))
+ ) : (
+
+
+
No profiles found
+
+ Try adjusting your search or filter to find what you're looking for.
+
+
+
+
+
+ )}
+
+ )}
);
diff --git a/lib/client/schema.ts b/lib/client/schema.ts
index 50cf947e..92673e80 100644
--- a/lib/client/schema.ts
+++ b/lib/client/schema.ts
@@ -9,9 +9,9 @@ export interface ProfileData {
conflictStyle: string;
description: string;
contactInfo: string;
- intellectualInterests: { interest?: { name?: string } }[];
- causeAreas: { causeArea?: { name?: string } }[];
- desiredConnections: { connection?: { name?: string } }[];
- promptAnswers: { prompt?: string; answer?: string }[];
+ intellectualInterests: { interest?: { name?: string, id?: string } }[];
+ causeAreas: { causeArea?: { name?: string, id?: string } }[];
+ desiredConnections: { connection?: { name?: string, id?: string } }[];
+ promptAnswers: { prompt?: string; answer?: string, id?: string }[];
};
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a4b7524c..e8358dd8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"resend": "^4.7.0",
+ "use-debounce": "^10.0.5",
"uuid": "^11.1.0"
},
"devDependencies": {
@@ -9308,6 +9309,17 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-debounce": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz",
+ "integrity": "sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==",
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index 568796df..70787ee4 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"resend": "^4.7.0",
+ "use-debounce": "^10.0.5",
"uuid": "^11.1.0"
},
"devDependencies": {