Skip to content

Commit db9a55e

Browse files
committed
improvements
1 parent c3c35a6 commit db9a55e

51 files changed

Lines changed: 1133 additions & 413 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/(app)/cities/[id]/page.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { useParams } from "next/navigation";
5+
import { City, Company, UserProfile } from "@/types";
6+
import { MapPin } from "lucide-react";
7+
import { AlumniCard } from "@/components/directory/AlumniCard";
8+
9+
export default function CityDetailPage() {
10+
const { id } = useParams<{ id: string }>();
11+
const [city, setCity] = useState<City | null>(null);
12+
const [members, setMembers] = useState<UserProfile[]>([]);
13+
const [companies, setCompanies] = useState<Company[]>([]);
14+
15+
useEffect(() => {
16+
Promise.all([
17+
fetch(`/api/cities/${id}`).then((r) => r.json()),
18+
fetch(`/api/users?cityId=${id}`).then((r) => r.json()),
19+
fetch("/api/companies").then((r) => r.json()),
20+
]).then(([city, users, allCompanies]: [City, UserProfile[], Company[]]) => {
21+
setCity(city);
22+
setMembers(users);
23+
setCompanies(allCompanies);
24+
}).catch(console.error);
25+
}, [id]);
26+
27+
if (!city) return null;
28+
29+
return (
30+
<div className="space-y-6">
31+
<div className="flex items-center gap-4">
32+
<MapPin className="h-10 w-10 text-muted-foreground" />
33+
<div>
34+
<h1 className="text-2xl font-semibold">{city.name}</h1>
35+
<p className="text-sm text-muted-foreground">{members.length} {members.length === 1 ? "member" : "members"}</p>
36+
</div>
37+
</div>
38+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
39+
{members.map((u) => (
40+
<AlumniCard key={u.uid} profile={u} companies={companies} />
41+
))}
42+
</div>
43+
{members.length === 0 && (
44+
<p className="text-center text-muted-foreground py-12">No members in this city yet.</p>
45+
)}
46+
</div>
47+
);
48+
}

app/(app)/cities/page.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { City, UserProfile } from "@/types";
5+
import { CityCard } from "@/components/cities/CityCard";
6+
import { Input } from "@/components/ui/input";
7+
8+
export default function CitiesPage() {
9+
const [allCities, setAllCities] = useState<City[]>([]);
10+
const [memberCounts, setMemberCounts] = useState<Record<string, number>>({});
11+
const [filter, setFilter] = useState("");
12+
13+
useEffect(() => {
14+
Promise.all([
15+
fetch("/api/cities").then((r) => r.json()),
16+
fetch("/api/users").then((r) => r.json()),
17+
]).then(([cities, users]: [City[], UserProfile[]]) => {
18+
setAllCities(cities);
19+
const counts: Record<string, number> = {};
20+
users.forEach((u) => { if (u.cityId) counts[u.cityId] = (counts[u.cityId] ?? 0) + 1; });
21+
setMemberCounts(counts);
22+
}).catch(console.error);
23+
}, []);
24+
25+
const filtered = allCities
26+
.filter((c) => !filter || c.name.toLowerCase().includes(filter.toLowerCase()))
27+
.sort((a, b) => (memberCounts[b.id] ?? 0) - (memberCounts[a.id] ?? 0));
28+
29+
return (
30+
<div className="space-y-6">
31+
<h1 className="text-2xl font-semibold">Cities</h1>
32+
<Input
33+
placeholder="Search cities…"
34+
value={filter}
35+
onChange={(e) => setFilter(e.target.value)}
36+
className="max-w-sm"
37+
/>
38+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
39+
{filtered.map((c) => (
40+
<CityCard key={c.id} city={c} memberCount={memberCounts[c.id] ?? 0} />
41+
))}
42+
</div>
43+
{filtered.length === 0 && (
44+
<p className="text-center text-muted-foreground py-12">No cities found.</p>
45+
)}
46+
</div>
47+
);
48+
}

app/(app)/companies/[id]/page.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { useParams } from "next/navigation";
5+
import { Company, UserProfile } from "@/types";
6+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7+
import { AlumniCard } from "@/components/directory/AlumniCard";
8+
9+
export default function CompanyDetailPage() {
10+
const { id } = useParams<{ id: string }>();
11+
const [company, setCompany] = useState<Company | null>(null);
12+
const [members, setMembers] = useState<UserProfile[]>([]);
13+
const [companies, setCompanies] = useState<Company[]>([]);
14+
15+
useEffect(() => {
16+
Promise.all([
17+
fetch(`/api/companies/${id}`).then((r) => r.json()),
18+
fetch(`/api/users?companyId=${id}`).then((r) => r.json()),
19+
fetch("/api/companies").then((r) => r.json()),
20+
]).then(([comp, users, allCompanies]: [Company, UserProfile[], Company[]]) => {
21+
setCompany(comp);
22+
setMembers(users);
23+
setCompanies(allCompanies);
24+
}).catch(console.error);
25+
}, [id]);
26+
27+
if (!company) return null;
28+
29+
const initials = company.name.slice(0, 2).toUpperCase();
30+
31+
return (
32+
<div className="space-y-6">
33+
<div className="flex items-center gap-4">
34+
<Avatar className="h-14 w-14 rounded-md">
35+
<AvatarImage src={company.logoUrl} alt={company.name} />
36+
<AvatarFallback className="rounded-md">{initials}</AvatarFallback>
37+
</Avatar>
38+
<div>
39+
<h1 className="text-2xl font-semibold">{company.name}</h1>
40+
<p className="text-sm text-muted-foreground">{members.length} {members.length === 1 ? "member" : "members"}</p>
41+
</div>
42+
</div>
43+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
44+
{members.map((u) => (
45+
<AlumniCard key={u.uid} profile={u} companies={companies} />
46+
))}
47+
</div>
48+
{members.length === 0 && (
49+
<p className="text-center text-muted-foreground py-12">No members yet.</p>
50+
)}
51+
</div>
52+
);
53+
}

app/(app)/companies/page.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { Company, UserProfile } from "@/types";
5+
import { CompanyFilters } from "@/components/companies/CompanyFilters";
6+
import { CompanyCard } from "@/components/companies/CompanyCard";
7+
8+
export default function CompaniesPage() {
9+
const [allCompanies, setAllCompanies] = useState<Company[]>([]);
10+
const [memberCounts, setMemberCounts] = useState<Record<string, number>>({});
11+
const [nameFilter, setNameFilter] = useState("");
12+
13+
useEffect(() => {
14+
Promise.all([
15+
fetch("/api/companies").then((r) => r.json()),
16+
fetch("/api/users").then((r) => r.json()),
17+
]).then(([companies, users]: [Company[], UserProfile[]]) => {
18+
setAllCompanies(companies);
19+
const counts: Record<string, number> = {};
20+
users.forEach((u) => u.companyIds.forEach((id) => { counts[id] = (counts[id] ?? 0) + 1; }));
21+
setMemberCounts(counts);
22+
}).catch(console.error);
23+
}, []);
24+
25+
const filtered = allCompanies
26+
.filter((c) => !nameFilter || c.name.toLowerCase().includes(nameFilter.toLowerCase()))
27+
.sort((a, b) => (memberCounts[b.id] ?? 0) - (memberCounts[a.id] ?? 0));
28+
29+
return (
30+
<div className="space-y-6">
31+
<h1 className="text-2xl font-semibold">Companies</h1>
32+
<CompanyFilters name={nameFilter} onNameChange={setNameFilter} />
33+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
34+
{filtered.map((c) => (
35+
<CompanyCard key={c.id} company={c} memberCount={memberCounts[c.id] ?? 0} />
36+
))}
37+
</div>
38+
{filtered.length === 0 && (
39+
<p className="text-center text-muted-foreground py-12">No companies found.</p>
40+
)}
41+
</div>
42+
);
43+
}

app/(app)/directory/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
"use client";
22

33
import { useEffect, useState } from "react";
4-
import { UserProfile, Organization } from "@/types";
4+
import { UserProfile, Company } from "@/types";
55
import { DirectoryFilters } from "@/components/directory/DirectoryFilters";
66
import { AlumniCard } from "@/components/directory/AlumniCard";
77

88
export default function DirectoryPage() {
99
const [allUsers, setAllUsers] = useState<UserProfile[]>([]);
10-
const [organizations, setOrganizations] = useState<Organization[]>([]);
10+
const [companies, setCompanies] = useState<Company[]>([]);
1111
const [nameFilter, setNameFilter] = useState("");
1212
const [classYearFilter, setClassYearFilter] = useState("");
1313

1414
useEffect(() => {
1515
Promise.all([
1616
fetch("/api/users").then((r) => r.json()),
17-
fetch("/api/organizations").then((r) => r.json()),
18-
]).then(([users, orgs]) => {
17+
fetch("/api/companies").then((r) => r.json()),
18+
]).then(([users, comps]) => {
1919
setAllUsers(users);
20-
setOrganizations(orgs);
20+
setCompanies(comps);
2121
});
2222
}, []);
2323

@@ -40,7 +40,7 @@ export default function DirectoryPage() {
4040
/>
4141
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
4242
{filtered.map((u) => (
43-
<AlumniCard key={u.uid} profile={u} organizations={organizations} />
43+
<AlumniCard key={u.uid} profile={u} companies={companies} />
4444
))}
4545
</div>
4646
{filtered.length === 0 && (

app/(app)/feed/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default function FeedPage() {
55
return (
66
<div className="space-y-6">
77
<div className="flex items-center justify-between">
8-
<h1 className="text-2xl font-semibold">Feed</h1>
8+
<h1 className="text-2xl font-semibold">Posts</h1>
99
<CreatePostDialog />
1010
</div>
1111
<PostList />

app/(app)/layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ export default async function AppLayout({ children }: { children: React.ReactNod
1919
<>
2020
<Header />
2121
<main className="mx-auto max-w-6xl px-4 py-6">{children}</main>
22+
<footer className="mt-12 py-6 text-center text-xs text-muted-foreground space-y-1">
23+
<p>&copy; {new Date().getFullYear()} Cornell AppDev Alumni</p>
24+
<p>
25+
Feedback?{" "}
26+
<a href="mailto:admin@alumni.cornellappdev.com" className="hover:underline">
27+
admin@alumni.cornellappdev.com
28+
</a>
29+
{" · "}
30+
<a href="https://github.com/cuappdev/alumni-website" target="_blank" rel="noopener noreferrer" className="hover:underline">
31+
GitHub
32+
</a>
33+
</p>
34+
</footer>
2235
</>
2336
);
2437
}

app/(app)/organizations/page.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

app/(app)/profile/[uid]/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { use, useEffect, useState } from "react";
44
import { notFound } from "next/navigation";
55
import { ProfileHeader } from "@/components/profile/ProfileHeader";
6-
import { UserProfile, Organization } from "@/types";
6+
import { UserProfile, Company } from "@/types";
77

88
export default function ProfilePage({
99
params,
@@ -12,15 +12,15 @@ export default function ProfilePage({
1212
}) {
1313
const { uid } = use(params);
1414
const [profile, setProfile] = useState<UserProfile | null | "loading">("loading");
15-
const [organizations, setOrganizations] = useState<Organization[]>([]);
15+
const [companies, setCompanies] = useState<Company[]>([]);
1616

1717
useEffect(() => {
1818
Promise.all([
1919
fetch(`/api/users/${uid}`).then((r) => (r.ok ? r.json() : null)),
20-
fetch("/api/organizations").then((r) => r.json()),
21-
]).then(([p, o]) => {
20+
fetch("/api/companies").then((r) => r.json()),
21+
]).then(([p, c]) => {
2222
setProfile(p);
23-
setOrganizations(o);
23+
setCompanies(c);
2424
});
2525
}, [uid]);
2626

@@ -36,7 +36,7 @@ export default function ProfilePage({
3636

3737
return (
3838
<div className="max-w-2xl mx-auto">
39-
<ProfileHeader initialProfile={profile} organizations={organizations} />
39+
<ProfileHeader initialProfile={profile} initialCompanies={companies} />
4040
</div>
4141
);
4242
}

app/api/cities/[id]/route.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { adminDb } from "@/lib/firebase/admin";
3+
import { getTokens } from "next-firebase-auth-edge";
4+
import { authConfig } from "@/lib/firebase/auth-edge";
5+
6+
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
7+
const tokens = await getTokens(request.cookies, authConfig);
8+
if (!tokens) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
9+
10+
const { id } = await params;
11+
const doc = await adminDb.collection("cities").doc(id).get();
12+
if (!doc.exists) return NextResponse.json({ error: "Not found" }, { status: 404 });
13+
14+
return NextResponse.json({ id: doc.id, ...doc.data() });
15+
}

0 commit comments

Comments
 (0)