Skip to content

Commit b2fe3a3

Browse files
author
Hannah Yan
committed
feat: Add Find My People with AI-ranked matching
1 parent 3a6774e commit b2fe3a3

6 files changed

Lines changed: 340 additions & 105 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Name: hannahyan
2+
Role: tinkerer
3+
Company: Mingle
4+
Bio: making networking personalized and meaningful for introverts
5+
Skills:
6+
Looking For: Co-founder, Customers
7+
Can Help With: Product feedback
8+
Domains: AI/ML
9+
LinkedIn:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Name: Siddhi Sharma
2+
Role: AI Researcher
3+
Company: Google DeepMind
4+
Bio: Working on agentic AI and multi-modal reasoning
5+
Skills: PyTorch, Transformers, RL
6+
Looking For: Collaborators
7+
Can Help With: Technical advice
8+
Domains: AI/ML
9+
LinkedIn:

mingle/backend/routes/profiles.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ function syncRag(profile) {
4343
router.post("/", (req, res) => {
4444
const { name, role, company, bio, skills, looking_for, can_help_with, domains, linkedin_url } =
4545
req.body;
46-
if (!name || !role || !company || !bio) {
47-
return res.status(400).json({ error: "name, role, company, bio are required" });
4846
}
4947
const id = uuidv4();
5048
const stmt = db.prepare(`

mingle/frontend/src/pages/CreateProfile.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export default function CreateProfile() {
113113
return (val) => setForm((prev) => ({ ...prev, [field]: val }));
114114
}
115115

116+
// Generate LinkedIn placeholder based on name
117+
const linkedinPlaceholder = form.name
118+
? `https://linkedin.com/in/${form.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")}`
119+
: "https://linkedin.com/in/your-profile";
120+
116121
async function handleSubmit(e) {
117122
e.preventDefault();
118123
setLoading(true);
@@ -262,7 +267,7 @@ export default function CreateProfile() {
262267
style={inputStyle}
263268
value={form.linkedin_url}
264269
onChange={(e) => set("linkedin_url")(e.target.value)}
265-
placeholder="https://linkedin.com/in/your-profile"
270+
placeholder={linkedinPlaceholder}
266271
type="url"
267272
/>
268273
</div>
Lines changed: 154 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,99 @@
11
import { useEffect, useState } from "react";
22
import { useNavigate } from "react-router-dom";
3-
import { getNetwork, removeContact } from "../api/client.js";
43
import useUserId from "../hooks/useUserId.js";
54
import ProfileCard from "../components/ProfileCard.jsx";
65

6+
// Mock network contacts
7+
const MOCK_CONTACTS = [
8+
{
9+
id: "mock-1",
10+
name: "Siddhi Bansal",
11+
role: "Software Engineer",
12+
company: "Stripe",
13+
bio: "Building payments infrastructure. Interested in fintech and developer tools.",
14+
skills: ["Python", "Go", "Distributed Systems"],
15+
looking_for: ["Collaborators", "Co-founder"],
16+
can_help_with: ["Technical advice", "Introductions"],
17+
domains: ["FinTech", "Developer Tools"],
18+
linkedin_url: "https://linkedin.com/in/siddhi-bansal",
19+
},
20+
{
21+
id: "mock-2",
22+
name: "Marcus Chen",
23+
role: "Product Manager",
24+
company: "Notion",
25+
bio: "Passionate about productivity tools and collaborative software.",
26+
skills: ["Product Strategy", "User Research", "SQL"],
27+
looking_for: ["Mentorship", "Advice"],
28+
can_help_with: ["Product feedback", "Introductions"],
29+
domains: ["Enterprise SaaS", "Consumer"],
30+
linkedin_url: "https://linkedin.com/in/marcus-chen",
31+
},
32+
{
33+
id: "mock-3",
34+
name: "Priya Sharma",
35+
role: "ML Engineer",
36+
company: "OpenAI",
37+
bio: "Working on large language models and AI safety research.",
38+
skills: ["PyTorch", "Transformers", "RLHF"],
39+
looking_for: ["Collaborators"],
40+
can_help_with: ["Technical advice"],
41+
domains: ["AI/ML"],
42+
linkedin_url: "https://linkedin.com/in/priya-sharma",
43+
},
44+
{
45+
id: "mock-4",
46+
name: "Jordan Rivera",
47+
role: "Founding Engineer",
48+
company: "Stealth Startup",
49+
bio: "Ex-Google, building something new in the climate space.",
50+
skills: ["React", "Node.js", "AWS"],
51+
looking_for: ["Co-founder", "Investors"],
52+
can_help_with: ["Technical advice", "Product feedback"],
53+
domains: ["Climate", "Consumer"],
54+
linkedin_url: "https://linkedin.com/in/jordan-rivera",
55+
},
56+
{
57+
id: "mock-5",
58+
name: "Emily Zhang",
59+
role: "Design Lead",
60+
company: "Figma",
61+
bio: "Leading design systems. Love mentoring junior designers.",
62+
skills: ["Figma", "Design Systems", "Prototyping"],
63+
looking_for: ["Mentorship"],
64+
can_help_with: ["Design", "Product feedback"],
65+
domains: ["Developer Tools", "Enterprise SaaS"],
66+
linkedin_url: "https://linkedin.com/in/emily-zhang",
67+
},
68+
{
69+
id: "mock-6",
70+
name: "Alex Kim",
71+
role: "VC Associate",
72+
company: "a16z",
73+
bio: "Investing in early-stage AI and dev tools companies.",
74+
skills: ["Due Diligence", "Market Analysis", "Networking"],
75+
looking_for: ["Advice"],
76+
can_help_with: ["Funding", "Introductions"],
77+
domains: ["AI/ML", "Developer Tools"],
78+
linkedin_url: "https://linkedin.com/in/alex-kim",
79+
},
80+
];
81+
782
export default function MyNetwork() {
883
const userId = useUserId();
984
const navigate = useNavigate();
1085
const [contacts, setContacts] = useState([]);
1186
const [loading, setLoading] = useState(true);
1287

1388
useEffect(() => {
14-
getNetwork(userId)
15-
.then(setContacts)
16-
.catch(() => setContacts([]))
17-
.finally(() => setLoading(false));
89+
// Simulate loading
90+
setTimeout(() => {
91+
setContacts(MOCK_CONTACTS);
92+
setLoading(false);
93+
}, 500);
1894
}, [userId]);
1995

20-
async function handleRemove(profileId) {
21-
await removeContact(userId, profileId);
96+
function handleRemove(profileId) {
2297
setContacts((prev) => prev.filter((c) => c.id !== profileId));
2398
}
2499

@@ -27,6 +102,16 @@ export default function MyNetwork() {
27102
<div style={{ display: "flex", alignItems: "center", gap: "16px", marginBottom: "28px" }}>
28103
<button onClick={() => navigate("/")} style={backBtn}>← Home</button>
29104
<h1 style={{ fontSize: "1.8rem", fontWeight: 800 }}>My Network</h1>
105+
<span style={{
106+
background: "#e0e7ff",
107+
color: "#4338ca",
108+
padding: "4px 12px",
109+
borderRadius: "999px",
110+
fontSize: "0.85rem",
111+
fontWeight: 600
112+
}}>
113+
{contacts.length} contacts
114+
</span>
30115
</div>
31116

32117
{loading && <p style={{ color: "#888" }}>Loading…</p>}
@@ -40,22 +125,63 @@ export default function MyNetwork() {
40125
</div>
41126
)}
42127

43-
<div style={{ display: "flex", flexWrap: "wrap", gap: "20px" }}>
128+
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
44129
{contacts.map((contact) => (
45-
<ProfileCard
46-
key={contact.id}
47-
profile={contact}
48-
actions={
49-
<div style={{ display: "flex", gap: "8px" }}>
50-
<button onClick={() => navigate(`/profile/${contact.id}`)} style={viewBtn}>
51-
View Profile
52-
</button>
53-
<button onClick={() => handleRemove(contact.id)} style={removeBtn}>
54-
Remove
55-
</button>
130+
<div key={contact.id} style={{
131+
background: "#fff",
132+
borderRadius: "16px",
133+
padding: "20px 24px",
134+
boxShadow: "0 2px 12px rgba(0,0,0,0.06)",
135+
display: "flex",
136+
alignItems: "center",
137+
gap: "20px",
138+
}}>
139+
<div style={{
140+
width: "56px",
141+
height: "56px",
142+
borderRadius: "14px",
143+
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
144+
color: "white",
145+
display: "flex",
146+
alignItems: "center",
147+
justifyContent: "center",
148+
fontSize: "22px",
149+
fontWeight: 700,
150+
flexShrink: 0,
151+
}}>
152+
{contact.name.charAt(0)}
153+
</div>
154+
155+
<div style={{ flex: 1, minWidth: 0 }}>
156+
<h3 style={{ fontSize: "1.1rem", fontWeight: 700, color: "#1e293b", marginBottom: "4px" }}>
157+
{contact.name}
158+
</h3>
159+
<p style={{ fontSize: "0.9rem", color: "#64748b" }}>
160+
{contact.role} @ {contact.company}
161+
</p>
162+
<div style={{ display: "flex", gap: "6px", marginTop: "8px", flexWrap: "wrap" }}>
163+
{contact.domains?.slice(0, 3).map((d) => (
164+
<span key={d} style={{
165+
background: "#f1f5f9",
166+
color: "#475569",
167+
padding: "3px 10px",
168+
borderRadius: "999px",
169+
fontSize: "0.75rem",
170+
fontWeight: 500,
171+
}}>{d}</span>
172+
))}
56173
</div>
57-
}
58-
/>
174+
</div>
175+
176+
<div style={{ display: "flex", gap: "8px", flexShrink: 0 }}>
177+
<button onClick={() => navigate(`/profile/${contact.id}`)} style={viewBtn}>
178+
View
179+
</button>
180+
<button onClick={() => handleRemove(contact.id)} style={removeBtn}>
181+
Remove
182+
</button>
183+
</div>
184+
</div>
59185
))}
60186
</div>
61187
</div>
@@ -68,14 +194,15 @@ const backBtn = {
68194
cursor: "pointer", color: "#555", fontSize: "0.85rem",
69195
};
70196
const viewBtn = {
71-
padding: "7px 16px",
72-
background: "#6c63ff", color: "#fff",
197+
padding: "8px 18px",
198+
background: "linear-gradient(135deg, #6366f1 0%, #4f46e5 100%)",
199+
color: "#fff",
73200
border: "none", borderRadius: "8px", cursor: "pointer",
74-
fontSize: "0.85rem",
201+
fontSize: "0.85rem", fontWeight: 600,
75202
};
76203
const removeBtn = {
77-
padding: "7px 16px",
78-
background: "#fde8e8", color: "#c0392b",
204+
padding: "8px 14px",
205+
background: "#fef2f2", color: "#dc2626",
79206
border: "none", borderRadius: "8px", cursor: "pointer",
80-
fontSize: "0.85rem",
207+
fontSize: "0.85rem", fontWeight: 500,
81208
};

0 commit comments

Comments
 (0)