mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-05-08 15:14:29 -04:00
Add core values
This commit is contained in:
@@ -16,6 +16,17 @@ export async function GET() {
|
||||
cacheStrategy: cacheStrategy,
|
||||
});
|
||||
|
||||
const coreValues = await prisma.value.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
orderBy: {
|
||||
name: 'asc'
|
||||
},
|
||||
cacheStrategy: cacheStrategy,
|
||||
});
|
||||
|
||||
const causeAreas = await prisma.causeArea.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
@@ -38,7 +49,7 @@ export async function GET() {
|
||||
cacheStrategy: cacheStrategy,
|
||||
});
|
||||
|
||||
return NextResponse.json({ interests, causeAreas, connections });
|
||||
return NextResponse.json({ interests, coreValues, causeAreas, connections });
|
||||
} catch (error) {
|
||||
console.error('Error fetching interests:', error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -26,6 +26,11 @@ export async function GET() {
|
||||
connection: true
|
||||
},
|
||||
},
|
||||
coreValues: {
|
||||
include: {
|
||||
value: true
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export async function GET(
|
||||
include: {
|
||||
intellectualInterests: { include: { interest: true } },
|
||||
causeAreas: { include: { causeArea: true } },
|
||||
coreValues: { include: { value: true } },
|
||||
desiredConnections: { include: { connection: true } },
|
||||
promptAnswers: true,
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function GET(request: Request) {
|
||||
const minIntroversion = url.searchParams.get("minIntroversion");
|
||||
const maxIntroversion = url.searchParams.get("maxIntroversion");
|
||||
const interests = url.searchParams.get("interests")?.split(",").filter(Boolean) || [];
|
||||
const coreValues = url.searchParams.get("coreValues")?.split(",").filter(Boolean) || [];
|
||||
const causeAreas = url.searchParams.get("causeAreas")?.split(",").filter(Boolean) || [];
|
||||
const connections = url.searchParams.get("connections")?.split(",").filter(Boolean) || [];
|
||||
const searchQuery = url.searchParams.get("search") || "";
|
||||
@@ -96,6 +97,22 @@ export async function GET(request: Request) {
|
||||
};
|
||||
}
|
||||
|
||||
// AND
|
||||
if (coreValues.length > 0) {
|
||||
where.profile = {
|
||||
...where.profile,
|
||||
AND: coreValues.map((name) => ({
|
||||
coreValues: {
|
||||
some: {
|
||||
value: {
|
||||
name: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// OR
|
||||
if (connections.length > 0) {
|
||||
where.profile = {
|
||||
@@ -179,6 +196,7 @@ export async function GET(request: Request) {
|
||||
profile: {
|
||||
include: {
|
||||
intellectualInterests: {include: {interest: true}},
|
||||
coreValues: {include: {value: true}},
|
||||
causeAreas: {include: {causeArea: true}},
|
||||
desiredConnections: {include: {connection: true}},
|
||||
promptAnswers: true,
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function POST(req: Request) {
|
||||
}
|
||||
|
||||
const data = await req.json();
|
||||
const {profile, image, name, interests = [], connections = []} = data;
|
||||
const {profile, image, name, interests = [], connections = [], coreValues = []} = data;
|
||||
|
||||
Object.keys(profile).forEach(key => {
|
||||
if (profile[key] === '' || !profile[key]) {
|
||||
@@ -44,65 +44,56 @@ export async function POST(req: Request) {
|
||||
},
|
||||
});
|
||||
|
||||
// Process interests if any
|
||||
if (interests.length > 0 && updatedUser.profile) {
|
||||
// First, find or create all interests
|
||||
const interestOperations = interests.map((interest: { id?: string; name: string }) =>
|
||||
prisma.interest.upsert({
|
||||
where: {id: interest.id || ''},
|
||||
update: {name: interest.name},
|
||||
create: {name: interest.name},
|
||||
})
|
||||
);
|
||||
const modelMap = {
|
||||
interest: prisma.interest,
|
||||
profileInterest: prisma.profileInterest,
|
||||
connection: prisma.connection,
|
||||
profileConnection: prisma.profileConnection,
|
||||
value: prisma.value,
|
||||
profileValue: prisma.profileValue,
|
||||
} as const;
|
||||
|
||||
const createdInterests = await Promise.all(interestOperations);
|
||||
async function handleFeatures(features, attribute: string, profileAttribute: string, idName: string) {
|
||||
// Process interests if any
|
||||
if (features.length > 0 && updatedUser.profile) {
|
||||
// First, find or create all features
|
||||
console.log('profile', profileAttribute, profileAttribute);
|
||||
const operations = features.map((feat: { id?: string; name: string }) =>
|
||||
modelMap[attribute].upsert({
|
||||
where: {id: feat.id || ''},
|
||||
update: {name: feat.name},
|
||||
create: {name: feat.name},
|
||||
})
|
||||
);
|
||||
|
||||
// Get the IDs of all created/updated interests
|
||||
const interestIds = createdInterests.map(interest => interest.id);
|
||||
const createdFeatures = await Promise.all(operations);
|
||||
|
||||
// First, remove all existing interests for this profile
|
||||
await prisma.profileInterest.deleteMany({
|
||||
where: {profileId: updatedUser.profile.id},
|
||||
});
|
||||
// Get the IDs of all created/updated features
|
||||
const ids = createdFeatures.map(v => v.id);
|
||||
|
||||
// Then, create new connections
|
||||
if (interestIds.length > 0) {
|
||||
await prisma.profileInterest.createMany({
|
||||
data: interestIds.map(interestId => ({
|
||||
profileId: updatedUser.profile!.id,
|
||||
interestId,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
// First, remove all existing interests for this profile
|
||||
await modelMap[profileAttribute].deleteMany({
|
||||
where: {profileId: updatedUser.profile.id},
|
||||
});
|
||||
|
||||
// Then, create new connections
|
||||
if (ids.length > 0) {
|
||||
await modelMap[profileAttribute].createMany({
|
||||
data: ids.map(id => ({
|
||||
profileId: updatedUser.profile!.id,
|
||||
[idName]: id,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connections.length > 0 && updatedUser.profile) {
|
||||
// First, find or create all interests
|
||||
const connectionOperations = connections.map((v: { id?: string; name: string }) =>
|
||||
prisma.connection.upsert({
|
||||
where: {id: v.id || ''},
|
||||
update: {name: v.name},
|
||||
create: {name: v.name},
|
||||
})
|
||||
);
|
||||
const createdConnections = await Promise.all(connectionOperations);
|
||||
const connectionIds = createdConnections.map(v => v.id);
|
||||
await prisma.profileConnection.deleteMany({
|
||||
where: {profileId: updatedUser.profile.id},
|
||||
});
|
||||
if (connectionIds.length > 0) {
|
||||
await prisma.profileConnection.createMany({
|
||||
data: connectionIds.map(id => ({
|
||||
profileId: updatedUser.profile!.id,
|
||||
connectionId: id,
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
await handleFeatures(interests, 'interest', 'profileInterest', 'interestId')
|
||||
await handleFeatures(connections, 'connection', 'profileConnection', 'connectionId')
|
||||
await handleFeatures(coreValues, 'value', 'profileValue', 'valueId')
|
||||
|
||||
return updatedUser;
|
||||
return updatedUser
|
||||
});
|
||||
|
||||
return NextResponse.json(result);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import {ChangeEvent, Suspense, useEffect, useRef, useState} from 'react';
|
||||
import {ChangeEvent, ReactNode, Suspense, useEffect, useRef, useState} from 'react';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {signOut, useSession} from 'next-auth/react';
|
||||
import Image from 'next/image';
|
||||
@@ -36,21 +36,33 @@ function RegisterComponent() {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [allInterests, setAllInterests] = useState<{ id: string, name: string }[]>([]);
|
||||
const [allConnections, setAllConnections] = useState<{ id: string, name: string }[]>([]);
|
||||
const [selectedInterests, setSelectedInterests] = useState<Set<string>>(new Set());
|
||||
const [selectedConnections, setSelectedConnections] = useState<Set<string>>(new Set());
|
||||
const [newInterest, setNewInterest] = useState('');
|
||||
const [newConnection, setNewConnection] = useState('');
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownRefC = useRef<HTMLDivElement>(null);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [showDropdownC, setShowDropdownC] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const router = useRouter();
|
||||
const {data: session, update} = useSession();
|
||||
const [showMoreInfo, setShowMoreInfo] = useState(false);
|
||||
|
||||
const hooks = Object.fromEntries(['interests', 'coreValues', 'description', 'connections'].map((id) => {
|
||||
const [showMoreInfo, setShowMoreInfo] = useState(false);
|
||||
const [newFeature, setNewFeature] = useState('');
|
||||
const [allFeatures, setAllFeatures] = useState<{ id: string, name: string }[]>([]);
|
||||
const [selectedFeatures, setSelectedFeatures] = useState<Set<string>>(new Set());
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
|
||||
return [id, {
|
||||
showMoreInfo,
|
||||
setShowMoreInfo,
|
||||
newFeature,
|
||||
setNewFeature,
|
||||
allFeatures,
|
||||
setAllFeatures,
|
||||
selectedFeatures,
|
||||
setSelectedFeatures,
|
||||
dropdownRef,
|
||||
showDropdown,
|
||||
setShowDropdown
|
||||
}]
|
||||
}));
|
||||
|
||||
const id = session?.user.id
|
||||
|
||||
@@ -81,17 +93,17 @@ function RegisterComponent() {
|
||||
}
|
||||
|
||||
// Set selected interests if any
|
||||
if (profile.intellectualInterests?.length > 0) {
|
||||
const ids = profile.intellectualInterests
|
||||
.map((pi: any) => pi.interest.id);
|
||||
setSelectedInterests(new Set(ids));
|
||||
function setSelectedFeatures(id: string, attribute: string, subAttribute: string) {
|
||||
const feature = profile[attribute];
|
||||
if (feature?.length > 0) {
|
||||
const ids = feature.map((pi: any) => pi[subAttribute].id);
|
||||
hooks[id].setSelectedFeatures(new Set(ids));
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.desiredConnections?.length > 0) {
|
||||
const ids = profile.desiredConnections
|
||||
.map((pi: any) => pi.connection.id);
|
||||
setSelectedConnections(new Set(ids));
|
||||
}
|
||||
setSelectedFeatures('interests', 'intellectualInterests', 'interest')
|
||||
setSelectedFeatures('coreValues', 'coreValues', 'value')
|
||||
setSelectedFeatures('connections', 'desiredConnections', 'connection')
|
||||
|
||||
setImages([])
|
||||
setKeys(profile?.images)
|
||||
@@ -113,80 +125,37 @@ function RegisterComponent() {
|
||||
}, [session]);
|
||||
|
||||
// Load existing interests and set up click-outside handler
|
||||
useEffect(() => {
|
||||
async function fetchInterests() {
|
||||
try {
|
||||
const res = await fetch('/api/interests');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setAllInterests(data.interests || []);
|
||||
for (const id of ['interests', 'coreValues', 'connections']) {
|
||||
useEffect(() => {
|
||||
async function fetchFeatures() {
|
||||
try {
|
||||
const res = await fetch('/api/interests');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
hooks[id].setAllFeatures(data[id] || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading' + id, error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading interests:', error);
|
||||
}
|
||||
}
|
||||
|
||||
fetchInterests();
|
||||
fetchFeatures();
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Load existing connections
|
||||
useEffect(() => {
|
||||
async function fetchConnections() {
|
||||
try {
|
||||
const res = await fetch('/api/interests');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setAllConnections(data.connections || []);
|
||||
// Close dropdown when clicking outside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const hook = hooks[id];
|
||||
const current = hook.dropdownRef.current;
|
||||
if (current && !current.contains(event.target as Node)) {
|
||||
hook.setShowDropdown(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading interests:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchConnections();
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRefC.current && !dropdownRefC.current.contains(event.target as Node)) {
|
||||
setShowDropdownC(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleConnection = (id: string) => {
|
||||
setSelectedConnections(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(id)) {
|
||||
newSet.delete(id);
|
||||
} else {
|
||||
newSet.add(id);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDownC = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setShowDropdownC(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -196,51 +165,6 @@ function RegisterComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
const toggleInterest = (interestId: string) => {
|
||||
setSelectedInterests(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(interestId)) {
|
||||
newSet.delete(interestId);
|
||||
} else {
|
||||
newSet.add(interestId);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewInterest();
|
||||
} else if (e.key === 'Escape') {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
const addNewInterest = (e?: React.FormEvent) => {
|
||||
if (e) e.preventDefault();
|
||||
const interestToAdd = newInterest.trim();
|
||||
if (!interestToAdd) return;
|
||||
|
||||
// Check if interest already exists (case-insensitive)
|
||||
const existingInterest = allInterests.find(
|
||||
i => i.name.toLowerCase() === interestToAdd.toLowerCase()
|
||||
);
|
||||
|
||||
if (existingInterest) {
|
||||
// Toggle selection if it exists
|
||||
toggleInterest(existingInterest.id);
|
||||
} else {
|
||||
// Add new interest
|
||||
const newInterestObj = {id: `new-${Date.now()}`, name: interestToAdd};
|
||||
setAllInterests(prev => [...prev, newInterestObj]);
|
||||
setSelectedInterests(prev => new Set(prev).add(newInterestObj.id));
|
||||
}
|
||||
|
||||
setNewInterest('');
|
||||
setShowDropdown(false);
|
||||
};
|
||||
|
||||
const handleImagesUpload = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
return handleImageUpload(e, false);
|
||||
}
|
||||
@@ -322,18 +246,15 @@ function RegisterComponent() {
|
||||
conflictStyle: conflictStyle as ConflictStyle,
|
||||
images: keys,
|
||||
},
|
||||
allConnections,
|
||||
interests: Array.from(selectedInterests).map(id => ({
|
||||
id: id.startsWith('new-') ? undefined : id,
|
||||
name: allInterests.find(i => i.id === id)?.name || id.replace('new-', '')
|
||||
})),
|
||||
connections: Array.from(selectedConnections).map(id => ({
|
||||
id: id,
|
||||
name: allConnections.find(i => i.id === id)?.name || id
|
||||
})),
|
||||
...(key && {image: key}),
|
||||
...(name && {name}),
|
||||
};
|
||||
for (const name of ['interests', 'connections', 'coreValues']) {
|
||||
data[name] = Array.from(hooks[name].selectedFeatures).map(id => ({
|
||||
id: id.startsWith('new-') ? undefined : id,
|
||||
name: hooks[name].allFeatures.find(i => i.id === id)?.name || id.replace('new-', '')
|
||||
}));
|
||||
}
|
||||
console.log('data', data)
|
||||
const response = await fetch('/api/user/update-profile', {
|
||||
method: 'POST',
|
||||
@@ -363,7 +284,265 @@ function RegisterComponent() {
|
||||
// const personalityOptions = Object.values(PersonalityType);
|
||||
const conflictOptions = Object.values(ConflictStyle);
|
||||
|
||||
const headingStyle = "block text-sm font-medium text-gray-700 dark:text-white mb-1";
|
||||
const headingStyle = "block text-base font-medium text-gray-700 dark:text-white mb-1";
|
||||
|
||||
function getDetails(id: string, brief: string, text: ReactNode) {
|
||||
const hook = hooks[id];
|
||||
const showMoreInfo = hook.showMoreInfo;
|
||||
const setShowMoreInfo = hook.setShowMoreInfo;
|
||||
return <>
|
||||
<div className="mt-2 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowMoreInfo(!showMoreInfo)}
|
||||
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 flex items-center"
|
||||
>
|
||||
{showMoreInfo ? 'Hide info' : brief}
|
||||
<svg
|
||||
className={`w-4 h-4 ml-1 transition-transform ${showMoreInfo ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
{showMoreInfo && (
|
||||
<div className="mt-2 p-3 bg-gray-50 dark:bg-gray-800 rounded-md text-sm text-gray-700 dark:text-gray-300">
|
||||
{text}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
interface DropdownConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
allowAdd: boolean;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
const dropdownConfig: DropdownConfig[] = [
|
||||
{
|
||||
id: 'connections', title: 'Desired Connections', allowAdd: false,
|
||||
content: null
|
||||
},
|
||||
{
|
||||
id: 'coreValues', title: 'Core Values', allowAdd: true,
|
||||
content: <>
|
||||
<p className="mt-2">
|
||||
When defining your core values on a platform meant for forming deep, lasting bonds, focus on what governs your
|
||||
choices, shapes your relationships, and anchors your sense of integrity—even when it's inconvenient. These
|
||||
aren't traits you aspire to signal; they’re principles you consistently return to when life is uncertain or
|
||||
difficult. Think in terms of how you treat others (e.g. intellectual honesty, compassion, loyalty), how you
|
||||
approach truth (e.g. humility, curiosity, critical thinking), and how you handle conflict or complexity (e.g.
|
||||
courage, nuance, responsibility). Be specific and truthful—avoid vague terms like “kindness” unless you can
|
||||
explain what it actually looks like in practice. The point isn't to be agreeable to everyone, but to be
|
||||
legible to those who share or deeply respect the values that define you. That clarity is what builds trust—and
|
||||
trust is the foundation of any bond worth keeping.
|
||||
</p>
|
||||
</>
|
||||
},
|
||||
{
|
||||
id: 'interests', title: 'Core Interests', allowAdd: true,
|
||||
content: <>
|
||||
<p className="mt-2">
|
||||
When selecting your core interests on a platform designed to foster deep, lasting
|
||||
bonds, think beyond surface-level hobbies and focus on what truly shapes how you see the world and
|
||||
connect with others. Choose interests that reveal how you think, what you care about deeply, and
|
||||
where you’re most engaged—intellectually, emotionally, or ethically. These might include long-term
|
||||
fascinations (like moral philosophy, storytelling, or systems thinking), enduring questions you
|
||||
wrestle with, or areas where you're actively growing. Don’t be afraid to show complexity or
|
||||
contradiction; honesty invites resonance. The goal isn’t to impress, but to be understood—by the
|
||||
kind of person who wants to know you for who you actually are, not just what you do for fun.
|
||||
</p>
|
||||
</>
|
||||
},
|
||||
]
|
||||
|
||||
function getDropdown({id, title, allowAdd, content}: DropdownConfig) {
|
||||
const hook = hooks[id];
|
||||
const newFeature = hook.newFeature;
|
||||
const setNewFeature = hook.setNewFeature;
|
||||
const showDropdown = hook.showDropdown;
|
||||
const setShowDropdown = hook.setShowDropdown;
|
||||
const allFeatures = hook.allFeatures;
|
||||
const setSelectedFeatures = hook.setSelectedFeatures;
|
||||
const setAllFeatures = hook.setAllFeatures;
|
||||
const dropdownRef = hook.dropdownRef;
|
||||
const selectedFeatures = hook.selectedFeatures;
|
||||
|
||||
const toggleFeature = (featureId: string) => {
|
||||
setSelectedFeatures(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(featureId)) {
|
||||
newSet.delete(featureId);
|
||||
} else {
|
||||
newSet.add(featureId);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewFeature();
|
||||
} else if (e.key === 'Escape') {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
const addNewFeature = (e?: React.FormEvent) => {
|
||||
if (e) e.preventDefault();
|
||||
const toAdd = newFeature.trim();
|
||||
if (!toAdd) return;
|
||||
|
||||
// Check if interest already exists (case-insensitive)
|
||||
const existingFeature = allFeatures.find(
|
||||
i => i.name.toLowerCase() === toAdd.toLowerCase()
|
||||
);
|
||||
|
||||
if (existingFeature) {
|
||||
// Toggle selection if it exists
|
||||
toggleFeature(existingFeature.id);
|
||||
} else {
|
||||
// Add new feature
|
||||
const newObj = {id: `new-${Date.now()}`, name: toAdd};
|
||||
setAllFeatures(prev => [...prev, newObj]);
|
||||
setSelectedFeatures(prev => new Set(prev).add(newObj.id));
|
||||
}
|
||||
|
||||
setNewFeature('');
|
||||
setShowDropdown(false);
|
||||
};
|
||||
|
||||
return <>
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<label className={headingStyle}>
|
||||
{title}
|
||||
</label>
|
||||
{content && getDetails(id, 'Guidance', content)}
|
||||
|
||||
<div className="relative">
|
||||
<div className="flex items-center border border-gray-300 rounded-md shadow-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={newFeature}
|
||||
maxLength={100}
|
||||
onChange={(e) => setNewFeature(e.target.value)}
|
||||
onFocus={() => setShowDropdown(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-md border-0 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="Type to search"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
className="px-3 py-2 border-l border-gray-300 text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<svg className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
{allowAdd &&
|
||||
<button
|
||||
type="button"
|
||||
onClick={addNewFeature}
|
||||
disabled={!newFeature.trim()}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-r-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
{(showDropdown || newFeature) && (
|
||||
<div
|
||||
className="absolute z-10 mt-1 w-full bg-white dark:bg-gray-900 shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
||||
{/* New interest option */}
|
||||
{allowAdd && newFeature && !allFeatures.some(i =>
|
||||
i.name.toLowerCase() === newFeature.toLowerCase()
|
||||
) && (
|
||||
<div
|
||||
className=" cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
onClick={() => addNewFeature()}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="font-normal ml-3 block truncate">
|
||||
Add "{newFeature}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered interests */}
|
||||
{allFeatures
|
||||
.filter(interest =>
|
||||
interest.name.toLowerCase().includes(newFeature.toLowerCase())
|
||||
)
|
||||
.map((interest) => (
|
||||
<div
|
||||
key={interest.id}
|
||||
className=" cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
onClick={() => {
|
||||
toggleFeature(interest.id);
|
||||
setNewFeature('');
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
checked={selectedFeatures.has(interest.id)}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
<span className="font-normal ml-3 block truncate">{interest.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Selected interests */}
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{Array.from(selectedFeatures).map(featureId => {
|
||||
const interest = allFeatures.find(i => i.id === featureId);
|
||||
if (!interest) return null;
|
||||
return (
|
||||
<span
|
||||
key={featureId}
|
||||
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800"
|
||||
>
|
||||
{interest.name}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleFeature(featureId);
|
||||
}}
|
||||
className="ml-1.5 inline-flex items-center justify-center h-4 w-4 rounded-full bg-blue-200 hover:bg-blue-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<span className="sr-only">Remove {interest.name}</span>
|
||||
<svg className="h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
|
||||
<path
|
||||
d="M4 3.293L6.646.646a.5.5 0 01.708.708L4.707 4l2.647 2.646a.5.5 0 01-.708.708L4 4.707l-2.646 2.647a.5.5 0 01-.708-.708L3.293 4 .646 1.354a.5.5 0 01.708-.708L4 3.293z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
@@ -505,217 +684,9 @@ function RegisterComponent() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative" ref={dropdownRefC}>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-2">
|
||||
Desired Connections
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="flex items-center border border-gray-300 rounded-md shadow-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={newConnection}
|
||||
onChange={(e) => setNewConnection(e.target.value)}
|
||||
onFocus={() => setShowDropdownC(true)}
|
||||
onKeyDown={handleKeyDownC}
|
||||
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-md border-0 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="Type to search"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDropdownC(!showDropdownC)}
|
||||
className="px-3 py-2 border-l border-gray-300 text-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<svg className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(showDropdownC) && (<div
|
||||
className="absolute z-10 mt-1 w-full bg-white dark:bg-gray-900 shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black dark:ring-white ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
||||
{allConnections
|
||||
.filter(v => v.name.toLowerCase().includes(newConnection.toLowerCase()))
|
||||
.map((v) => (
|
||||
<div
|
||||
key={v.id}
|
||||
className=" dark:text-white cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
onClick={() => {
|
||||
toggleConnection(v.id);
|
||||
setNewConnection('');
|
||||
// setNewInterest('');
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
checked={selectedConnections.has(v.id)}
|
||||
onChange={() => {
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<span className="font-normal ml-3 block truncate">{v.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{Array.from(selectedConnections).map(id => {
|
||||
const v = allConnections.find(i => i.id === id);
|
||||
if (!v) return null;
|
||||
return (
|
||||
<span
|
||||
key={id}
|
||||
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800"
|
||||
>
|
||||
{v.name}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleConnection(id);
|
||||
}}
|
||||
className="ml-1.5 inline-flex items-center justify-center h-4 w-4 rounded-full bg-blue-200 hover:bg-blue-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<span className="sr-only">Remove {v.name}</span>
|
||||
<svg className="h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
|
||||
<path
|
||||
d="M4 3.293L6.646.646a.5.5 0 01.708.708L4.707 4l2.647 2.646a.5.5 0 01-.708.708L4 4.707l-2.646 2.647a.5.5 0 01-.708-.708L3.293 4 .646 1.354a.5.5 0 01.708-.708L4 3.293z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<label className={headingStyle}>
|
||||
Core Interests
|
||||
</label>
|
||||
|
||||
<div className="relative">
|
||||
<div className="flex items-center border border-gray-300 rounded-md shadow-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={newInterest}
|
||||
maxLength={100}
|
||||
onChange={(e) => setNewInterest(e.target.value)}
|
||||
onFocus={() => setShowDropdown(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-l-md border-0 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="Type to search or add new interest"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
className="px-3 py-2 border-l border-gray-300 text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<svg className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addNewInterest}
|
||||
disabled={!newInterest.trim()}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-r-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(showDropdown || newInterest) && (
|
||||
<div
|
||||
className="absolute z-10 mt-1 w-full bg-white dark:bg-gray-900 shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
||||
{/* New interest option */}
|
||||
{newInterest && !allInterests.some(i =>
|
||||
i.name.toLowerCase() === newInterest.toLowerCase()
|
||||
) && (
|
||||
<div
|
||||
className=" cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
onClick={() => addNewInterest()}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="font-normal ml-3 block truncate">
|
||||
Add "{newInterest}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered interests */}
|
||||
{allInterests
|
||||
.filter(interest =>
|
||||
interest.name.toLowerCase().includes(newInterest.toLowerCase())
|
||||
)
|
||||
.map((interest) => (
|
||||
<div
|
||||
key={interest.id}
|
||||
className=" cursor-default select-none relative py-2 pl-3 pr-9 hover:bg-blue-50 dark:hover:bg-gray-700"
|
||||
onClick={() => {
|
||||
toggleInterest(interest.id);
|
||||
setNewInterest('');
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
checked={selectedInterests.has(interest.id)}
|
||||
onChange={() => {
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<span className="font-normal ml-3 block truncate">
|
||||
{interest.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Selected interests */}
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{Array.from(selectedInterests).map(interestId => {
|
||||
const interest = allInterests.find(i => i.id === interestId);
|
||||
if (!interest) return null;
|
||||
return (
|
||||
<span
|
||||
key={interestId}
|
||||
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800"
|
||||
>
|
||||
{interest.name}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleInterest(interestId);
|
||||
}}
|
||||
className="ml-1.5 inline-flex items-center justify-center h-4 w-4 rounded-full bg-blue-200 hover:bg-blue-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<span className="sr-only">Remove {interest.name}</span>
|
||||
<svg className="h-2 w-2" fill="currentColor" viewBox="0 0 8 8">
|
||||
<path
|
||||
d="M4 3.293L6.646.646a.5.5 0 01.708.708L4.707 4l2.647 2.646a.5.5 0 01-.708.708L4 4.707l-2.646 2.647a.5.5 0 01-.708-.708L3.293 4 .646 1.354a.5.5 0 01.708-.708L4 3.293z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{getDropdown(dropdownConfig[0])}
|
||||
{getDropdown(dropdownConfig[1])}
|
||||
{getDropdown(dropdownConfig[2])}
|
||||
|
||||
<div>
|
||||
<label htmlFor="introversion" className={headingStyle}>
|
||||
@@ -777,48 +748,46 @@ function RegisterComponent() {
|
||||
<div className="max-w-3xl w-full">
|
||||
<label htmlFor="description" className={headingStyle}>
|
||||
About You
|
||||
<p className="text-xs italic">
|
||||
<p className="text-sm italic">
|
||||
Feel free to include any relevant links (dating / friends docs, etc.), but consider copy-pasting
|
||||
the content here so that people can find you by keyword search.
|
||||
</p>
|
||||
</label>
|
||||
<div className="mt-2 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowMoreInfo(!showMoreInfo)}
|
||||
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 flex items-center"
|
||||
>
|
||||
{showMoreInfo ? 'Hide info' : 'Key details for fulfilling encounters'}
|
||||
<svg
|
||||
className={`w-4 h-4 ml-1 transition-transform ${showMoreInfo ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{showMoreInfo && (
|
||||
<div className="mt-2 p-3 bg-gray-50 dark:bg-gray-800 rounded-md text-sm text-gray-700 dark:text-gray-300">
|
||||
<p className="mt-2">To the extent that you are comfortable sharing, consider writing about:</p>
|
||||
<ul className="list-disc pl-5 space-y-1">
|
||||
<li>Your interests and what you're looking for: type of connection, activities to do, etc.</li>
|
||||
<li>Your availability and timezone</li>
|
||||
<li>What makes you unique</li>
|
||||
<li>Your expectations and boundaries</li>
|
||||
<li>Your intellectual interests (currently exploring, favorite, and least favorite)</li>
|
||||
<li>Your core values</li>
|
||||
<li>Your altruistic values: community engagement, social justice, and other cause areas</li>
|
||||
<li>Your level of education, hobbies, pets, habits, subcultures, diet, emotional sensitivity, sense of humor, ambition, organization, pet peeves, non-negotiables</li>
|
||||
<li>Your thinking style, results from evidence-based personality tests (e.g., Big 5)</li>
|
||||
<li>Your physical and mental health: some traits that rub people the wrong way, triggers, therapy, or what you are trying to improve</li>
|
||||
<li>If interested in romantic relationships, your love languages (giving and receiving), timeline, romantic orientation, family projects, work-life balance, financial goals / habits, career goals, housing situation (renting vs owning), and whether you would date someone who already has kids</li>
|
||||
<li>What you would like in your ideal person or connection—where they should be similar or different from your own description</li>
|
||||
<li>Conversation starters or questions</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{getDetails(
|
||||
'description',
|
||||
'Key details for fulfilling encounters',
|
||||
<>
|
||||
<p className="mt-2">To the extent that you are comfortable sharing, consider writing about:</p>
|
||||
<ul className="list-disc pl-5 space-y-1">
|
||||
<li>Your interests and what you're looking for: type of connection, activities to do, etc.</li>
|
||||
<li>Your availability and timezone</li>
|
||||
<li>What makes you unique</li>
|
||||
<li>Your expectations and boundaries</li>
|
||||
<li>Your intellectual interests (currently exploring, favorite, and least favorite)</li>
|
||||
<li>Your core values</li>
|
||||
<li>Your altruistic values: community engagement, social justice, and other cause areas</li>
|
||||
<li>Your level of education, hobbies, pets, habits, subcultures, diet, emotional sensitivity, sense
|
||||
of
|
||||
humor, ambition, organization, pet peeves, non-negotiables
|
||||
</li>
|
||||
<li>Your thinking style, results from evidence-based personality tests (e.g., Big 5)</li>
|
||||
<li>Your physical and mental health: some traits that rub people the wrong way, triggers, therapy,
|
||||
or what
|
||||
you are trying to improve
|
||||
</li>
|
||||
<li>If interested in romantic relationships, your love languages (giving and receiving), timeline,
|
||||
romantic orientation, family projects, work-life balance, financial goals / habits, career goals,
|
||||
housing situation (renting vs owning), and whether you would date someone who already has kids
|
||||
</li>
|
||||
<li>What you would like in your ideal person or connection—where they should be similar or different
|
||||
from
|
||||
your own description
|
||||
</li>
|
||||
<li>Conversation starters or questions</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
|
||||
@@ -11,6 +11,7 @@ interface FilterProps {
|
||||
filters: {
|
||||
gender: string;
|
||||
interests: string[];
|
||||
coreValues: string[];
|
||||
connections: string[];
|
||||
causeAreas: string[];
|
||||
searchQuery: string;
|
||||
@@ -28,6 +29,7 @@ interface FilterProps {
|
||||
export const dropdownConfig: { id: DropdownKey, name: string }[] = [
|
||||
{id: "connections", name: "Desired Connections"},
|
||||
{id: "interests", name: "Core Interests"},
|
||||
{id: "coreValues", name: "Core Values"},
|
||||
{id: "causeAreas", name: "Cause Areas"},
|
||||
]
|
||||
|
||||
@@ -69,6 +71,7 @@ export function ProfileFilters({filters, onFilterChange, onShowFilters, onToggle
|
||||
const data = await res.json() as Record<string, Item[]>;
|
||||
console.log(data);
|
||||
for (const [id, values] of Object.entries(data)) {
|
||||
console.log(id)
|
||||
dropDownStates[id].options.set(values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ const initialState = {
|
||||
minIntroversion: null as number | null,
|
||||
maxIntroversion: null as number | null,
|
||||
interests: [] as string[],
|
||||
coreValues: [] as string[],
|
||||
causeAreas: [] as string[],
|
||||
connections: [] as string[],
|
||||
searchQuery: '',
|
||||
forceRun: false,
|
||||
};
|
||||
|
||||
export type DropdownKey = 'interests' | 'causeAreas' | 'connections';
|
||||
export type DropdownKey = 'interests' | 'causeAreas' | 'connections' | 'coreValues';
|
||||
export type RangeKey = 'age' | 'introversion';
|
||||
// type OtherKey = 'gender' | 'searchQuery';
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ export function getProfile(url: string, header: any = null) {
|
||||
const tagsConfig: Tags[] = [
|
||||
{profileAttribute: 'desiredConnections', attribute: 'connection', title: 'Desired Connections'},
|
||||
{profileAttribute: 'intellectualInterests', attribute: 'interest', title: 'Core Interests'},
|
||||
{profileAttribute: 'coreValues', attribute: 'value', title: 'Core Values'},
|
||||
{profileAttribute: 'causeAreas', attribute: 'causeArea', title: 'Cause Areas'},
|
||||
]
|
||||
|
||||
@@ -293,7 +294,7 @@ export function getProfile(url: string, header: any = null) {
|
||||
key={idx}
|
||||
// className="px-3 py-1 text-sm bg-gray-100 rounded-full hover:bg-gray-200 transition"
|
||||
>
|
||||
• {value.prompt} {value.answer}
|
||||
<i>{value.prompt}</i><br/>{value.answer}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ProfileData {
|
||||
description: string;
|
||||
contactInfo: string;
|
||||
intellectualInterests: { interest?: { name?: string, id?: string } }[];
|
||||
coreValues: { value?: { name?: string, id?: string } }[];
|
||||
causeAreas: { causeArea?: { name?: string, id?: string } }[];
|
||||
desiredConnections: { connection?: { name?: string, id?: string } }[];
|
||||
promptAnswers: { prompt?: string; answer?: string, id?: string }[];
|
||||
|
||||
@@ -45,9 +45,10 @@ model Profile {
|
||||
// communicationPreferences CommunicationPreferences[]
|
||||
|
||||
// Many-to-many
|
||||
promptAnswers PromptAnswer[]
|
||||
promptAnswers PromptAnswer[]
|
||||
desiredConnections ProfileConnection[]
|
||||
intellectualInterests ProfileInterest[]
|
||||
coreValues ProfileValue[]
|
||||
causeAreas ProfileCauseArea[]
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
@@ -85,6 +86,12 @@ model Interest {
|
||||
users ProfileInterest[]
|
||||
}
|
||||
|
||||
model Value {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
users ProfileValue[]
|
||||
}
|
||||
|
||||
model CauseArea {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
@@ -113,6 +120,16 @@ model ProfileInterest {
|
||||
@@id([profileId, interestId])
|
||||
}
|
||||
|
||||
model ProfileValue {
|
||||
profileId String
|
||||
valueId String
|
||||
|
||||
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
||||
value Value @relation(fields: [valueId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([profileId, valueId])
|
||||
}
|
||||
|
||||
model ProfileCauseArea {
|
||||
profileId String
|
||||
causeAreaId String
|
||||
|
||||
@@ -15,6 +15,7 @@ async function main() {
|
||||
location: string;
|
||||
bio: string;
|
||||
interests: string[];
|
||||
values: string[];
|
||||
};
|
||||
|
||||
const profiles: ProfileBio[] = [
|
||||
@@ -25,7 +26,8 @@ async function main() {
|
||||
occupation: "Cognitive Science Researcher",
|
||||
location: "Berlin, Germany",
|
||||
bio: "I’m passionate about understanding the limits and mechanics of human reasoning. I spend weekends dissecting papers on decision theory and evenings debating moral uncertainty. If you know your way around LessWrong and thought experiments, we’ll get along.",
|
||||
interests: ["Bayesian epistemology", "AI alignment", "Effective Altruism", "Meditation", "Game Theory"]
|
||||
interests: ["Bayesian epistemology", "AI alignment", "Effective Altruism", "Meditation", "Game Theory"],
|
||||
values: ["Intellectualism", "Rationality", "Autonomy"]
|
||||
},
|
||||
{
|
||||
name: "Marcus",
|
||||
@@ -34,7 +36,8 @@ async function main() {
|
||||
occupation: "Software Engineer",
|
||||
location: "San Francisco, USA",
|
||||
bio: "Practicing instrumental rationality one well-calibrated belief at a time. Stoicism and startup life have taught me a lot about tradeoffs. Looking for someone who can argue in good faith and loves truth-seeking as much as I do.",
|
||||
interests: ["Stoicism", "Predictive processing", "Rational fiction", "Startups", "Causal inference"]
|
||||
interests: ["Stoicism", "Predictive processing", "Rational fiction", "Startups", "Causal inference"],
|
||||
values: ["Diplomacy", "Rationality", "Community"]
|
||||
},
|
||||
{
|
||||
name: "Aya",
|
||||
@@ -43,7 +46,8 @@ async function main() {
|
||||
occupation: "Philosophy PhD Candidate",
|
||||
location: "Oxford, UK",
|
||||
bio: "My research focuses on metaethics and formal logic, but my heart belongs to moral philosophy. I think a lot about personhood, consciousness, and the ethics of future civilizations. Let's talk about Rawls or Parfit over tea.",
|
||||
interests: ["Metaethics", "Consciousness", "Transhumanism", "Moral realism", "Formal logic"]
|
||||
interests: ["Metaethics", "Consciousness", "Transhumanism", "Moral realism", "Formal logic"],
|
||||
values: ["Radical Honesty", "Structure", "Sufficiency"]
|
||||
},
|
||||
{
|
||||
name: "David",
|
||||
@@ -52,7 +56,8 @@ async function main() {
|
||||
occupation: "Data Scientist",
|
||||
location: "Toronto, Canada",
|
||||
bio: "Former humanities major turned quant. Still fascinated by existential risk, the philosophy of science, and how to stay sane in an uncertain world. I'm here to meet people who think weird is a compliment.",
|
||||
interests: ["Probability theory", "Longtermism", "Epistemic humility", "Futurology", "Meditation"]
|
||||
interests: ["Probability theory", "Longtermism", "Epistemic humility", "Futurology", "Meditation"],
|
||||
values: ["Conservatism", "Ambition", "Idealism"]
|
||||
},
|
||||
{
|
||||
name: "Mei",
|
||||
@@ -61,23 +66,32 @@ async function main() {
|
||||
occupation: "Independent Writer",
|
||||
location: "Singapore",
|
||||
bio: "Writing essays on intellectual humility, the philosophy of language, and how thinking styles shape our lives. I appreciate calm reasoning, rigorous curiosity, and the beauty of well-defined concepts. Let's try to model each other's minds.",
|
||||
interests: ["Philosophy of language", "Bayesian reasoning", "Writing", "Dialectics", "Systems thinking"]
|
||||
interests: ["Philosophy of language", "Bayesian reasoning", "Writing", "Dialectics", "Systems thinking"],
|
||||
values: ["Emotional Merging", "Sufficiency", "Pragmatism"]
|
||||
}
|
||||
];
|
||||
|
||||
const interests = new Set<string>();
|
||||
const values = new Set<string>();
|
||||
|
||||
profiles.forEach(profile => {
|
||||
profile.interests.forEach(interest => {
|
||||
interests.add(interest);
|
||||
});
|
||||
profile.interests.forEach(v => interests.add(v));
|
||||
});
|
||||
|
||||
profiles.forEach(profile => {
|
||||
profile.values.forEach(v => values.add(v));
|
||||
});
|
||||
|
||||
console.log('Interests:', [...interests]);
|
||||
console.log('Values:', [...values]);
|
||||
|
||||
// Create some interests and cause areas
|
||||
await prisma.interest.createMany({
|
||||
data: [...interests].map(interest => ({name: interest})),
|
||||
data: [...interests].map(v => ({name: v})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
await prisma.value.createMany({
|
||||
data: [...values].map(v => ({name: v})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
@@ -102,6 +116,7 @@ async function main() {
|
||||
|
||||
// Get actual Interest & CauseArea objects
|
||||
const allInterests = await prisma.interest.findMany();
|
||||
const allValues = await prisma.value.findMany();
|
||||
const allCauseAreas = await prisma.causeArea.findMany();
|
||||
const allConnections = await prisma.connection.findMany();
|
||||
|
||||
@@ -130,10 +145,14 @@ async function main() {
|
||||
],
|
||||
},
|
||||
intellectualInterests: {
|
||||
// create: [...allInterests].map(interest => ({interestId: interest.id}) && Set(profile.interests))
|
||||
create: allInterests
|
||||
.filter(e => (new Set(profile.interests)).has(e.name)) // keep only elements in the set
|
||||
.map(e => ({interestId: e.id})) // map to object with `a` key
|
||||
.filter(e => (new Set(profile.interests)).has(e.name))
|
||||
.map(e => ({interestId: e.id}))
|
||||
},
|
||||
coreValues: {
|
||||
create: allValues
|
||||
.filter(e => (new Set(profile.values)).has(e.name))
|
||||
.map(e => ({valueId: e.id}))
|
||||
},
|
||||
causeAreas: {
|
||||
create: [
|
||||
|
||||
Reference in New Issue
Block a user