router.back()}>
Back
- router.push("/explore")}>
- Exit
+
+ Cancel
-
+
{isPending ? "Saving…" : "Save changes"}
@@ -168,11 +197,9 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
My Account
- {/* ═══ Personal Info ═══ */}
Personal Info
- {/* Avatar */}
{avatarPreview ? (
@@ -207,7 +234,6 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
/>
- {/* Name + Class Year inline */}
@@ -217,6 +243,7 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
+
+
+
+ setMajor(e.target.value)}
+ placeholder="e.g. Computer Science"
+ className="w-full border-b border-forum-medium-gray bg-transparent pb-1.5 font-dm-sans text-[15px] text-black outline-none"
+ />
+
- {/* ═══ Friends + Organizations — two columns ═══ */}
- {/* Friends column */}
Friends
@@ -266,7 +302,6 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
- {/* Organizations column */}
Organizations
@@ -291,12 +326,10 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
- {/* ═══ Interest Tags ═══ */}
Interest Tags
- {/* Selected topics */}
Topics
@@ -325,23 +358,26 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
- {/* Search for new tags */}
Suggested tags
- {SUGGESTION_TAGS.map((tag) => (
-
- {tag}
+ {filteredTags.map((tag) => (
+ toggleInterest(tag.id)}
+ aria-label={tag.label}
+ >
+ {tag.label}
))}
- {/* Organizations — link to orgs page */}
Organizations
@@ -356,137 +392,6 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
-
- {/* ═══ Friends + Organizations (bottom expanded view) ═══ */}
-
- {/* Friends expanded */}
-
-
-
- {/* Avatar large */}
-
- {profile.avatarUrl ? (
-
- ) : (
-
- {profile.displayName[0]?.toUpperCase()}
-
- )}
-
-
-
-
-
-
-
-
- {friends.map((friend) => (
-
-
- {friend.avatarUrl ? (
-
- ) : (
-
- {friend.displayName[0]?.toUpperCase()}
-
- )}
-
-
-
- {friend.displayName}
-
-
- @{friend.netId}
-
-
- {friend.classYear && (
-
- '{friend.classYear.slice(-2)}
-
- )}
-
- ))}
-
-
-
- ADD / EDIT MY FRIENDS LIST
-
-
-
-
- {/* Organizations expanded */}
-
-
-
-
-
-
-
-
-
- {[1, 2, 3, 4].map((i) => (
-
-
-
-
- Princeton TigerApps
-
-
- Design Lead
-
-
-
- Edit Role
-
-
- ))}
-
-
-
- ADD / EDIT MY ORGANIZATIONS
-
-
-
-
);
}
From 8c45a89459934cbf3f3454cfe917bc0f3065dcfa Mon Sep 17 00:00:00 2001
From: JiyaS12 <120071069+JiyaS12@users.noreply.github.com>
Date: Tue, 25 Aug 2026 17:22:29 -0500
Subject: [PATCH 2/3] Fix settings interests save and persistence
---
apps/database/src/schema/index.ts | 22 ++--
apps/web/src/actions/users.ts | 60 ++++++++--
.../app/(app)/settings/settings-client.tsx | 108 +++++++++++++-----
3 files changed, 145 insertions(+), 45 deletions(-)
diff --git a/apps/database/src/schema/index.ts b/apps/database/src/schema/index.ts
index 388f1f5..393f68d 100644
--- a/apps/database/src/schema/index.ts
+++ b/apps/database/src/schema/index.ts
@@ -16,15 +16,16 @@ import {
// ── Enums ─────────────────────────────────────────────────
export const eventTagEnum = pgEnum("event_tag", [
- "free food",
- "career",
+ "free-food",
+ "career-recruiting",
"research",
+ "stem",
"academics",
"tech",
"entrepreneurship",
- "politics",
- "visual arts",
- "performing arts",
+ "politics-policy",
+ "visual-arts",
+ "performing-arts",
"literature",
"culture",
"music",
@@ -32,12 +33,11 @@ export const eventTagEnum = pgEnum("event_tag", [
"athletics",
"religion",
"sustainability",
- "outdoors",
- "wellness",
- "community service",
- "speaker event",
- "social event",
- "stem",
+ "outdoor-adventure",
+ "wellness-self-care",
+ "community-service",
+ "speaker-event",
+ "social-event",
]);
export const campusRegionEnum = pgEnum("campus_region", [
diff --git a/apps/web/src/actions/users.ts b/apps/web/src/actions/users.ts
index 51b04e2..9c41f3d 100644
--- a/apps/web/src/actions/users.ts
+++ b/apps/web/src/actions/users.ts
@@ -12,10 +12,53 @@ import {
import { revalidatePath } from "next/cache";
import { auth } from "~/auth";
-// Derived from the DB schema so these can never drift from the pgEnum values
+const LEGACY_INTEREST_TAG_ALIASES: Record
= {
+ career: "career-recruiting",
+ academic: "research",
+ academics: "academics",
+ tech: "tech",
+ political: "politics-policy",
+ politics: "politics-policy",
+ art: "visual-arts",
+ visual: "visual-arts",
+ performance: "performing-arts",
+ performing: "performing-arts",
+ cultural: "culture",
+ culture: "culture",
+ sports: "athletics",
+ athletic: "athletics",
+ religious: "religion",
+ religion: "religion",
+ outdoor: "outdoor-adventure",
+ outdoors: "outdoor-adventure",
+ wellness: "wellness-self-care",
+ speaker: "speaker-event",
+ social: "social-event",
+ "free food": "free-food",
+ "free-food": "free-food",
+ "community service": "community-service",
+ "community-service": "community-service",
+ "politics policy": "politics-policy",
+ "visual arts": "visual-arts",
+ "performing arts": "performing-arts",
+ "wellness self care": "wellness-self-care",
+ "speaker event": "speaker-event",
+ "social event": "social-event",
+ stem: "stem",
+};
+
type InterestTag = (typeof eventTagEnum.enumValues)[number];
type CampusRegion = (typeof campusRegionEnum.enumValues)[number];
+function normalizeInterestTag(tag: string): string {
+ const normalized = tag.trim().toLowerCase().replace(/\s+/g, "-");
+ return LEGACY_INTEREST_TAG_ALIASES[normalized] ?? LEGACY_INTEREST_TAG_ALIASES[tag] ?? normalized;
+}
+
+function dedupeInterests(values: string[]): string[] {
+ return [...new Set(values.map((value) => normalizeInterestTag(value)).filter(Boolean))];
+}
+
export async function completeOnboarding(data: {
interests: string[];
classYear: string;
@@ -28,7 +71,6 @@ export async function completeOnboarding(data: {
const userId = session.user.id;
- // Update user profile
await db
.update(users)
.set({
@@ -40,18 +82,17 @@ export async function completeOnboarding(data: {
})
.where(eq(users.id, userId));
- // Insert interests
if (data.interests.length > 0) {
+ const normalizedInterests = dedupeInterests(data.interests);
await db.delete(userInterests).where(eq(userInterests.userId, userId));
await db.insert(userInterests).values(
- data.interests.map((tag) => ({
+ normalizedInterests.map((tag) => ({
userId,
tag: tag as InterestTag,
})),
);
}
- // Insert regions
if (data.regions.length > 0) {
await db.delete(userRegions).where(eq(userRegions.userId, userId));
await db.insert(userRegions).values(
@@ -129,6 +170,8 @@ export async function getUserProfile(): Promise {
.from(userInterests)
.where(eq(userInterests.userId, user.id));
+ const normalizedInterests = dedupeInterests(interests.map(({ tag }) => tag));
+
const regions = await db
.select({ region: userRegions.region })
.from(userRegions)
@@ -143,7 +186,7 @@ export async function getUserProfile(): Promise {
major: user.major,
avatarUrl: user.avatarUrl,
isOrgLeader: user.isOrgLeader,
- interests: interests.map((i) => i.tag),
+ interests: normalizedInterests,
regions: regions.map((r) => r.region),
};
}
@@ -169,10 +212,11 @@ export async function updateProfile(data: {
.where(eq(users.id, userId));
if (data.interests) {
+ const normalizedInterests = dedupeInterests(data.interests);
await db.delete(userInterests).where(eq(userInterests.userId, userId));
- if (data.interests.length > 0) {
+ if (normalizedInterests.length > 0) {
await db.insert(userInterests).values(
- data.interests.map((tag) => ({
+ normalizedInterests.map((tag) => ({
userId,
tag: tag as InterestTag,
})),
diff --git a/apps/web/src/app/(app)/settings/settings-client.tsx b/apps/web/src/app/(app)/settings/settings-client.tsx
index 80940e9..c98e98b 100644
--- a/apps/web/src/app/(app)/settings/settings-client.tsx
+++ b/apps/web/src/app/(app)/settings/settings-client.tsx
@@ -16,29 +16,80 @@ import { PageHeading, PageShell, SectionHeading } from "~/components/layout/page
import { Button } from "~/components/ui/button";
const INTEREST_TAGS = [
- { id: "free food", label: "free food" },
- { id: "tech", label: "technology" },
- { id: "stem", label: "science and engineering" },
- { id: "visual arts", label: "visual arts" },
- { id: "wellness", label: "fitness & health" },
- { id: "academics", label: "academics" },
- { id: "research", label: "research" },
- { id: "career", label: "career" },
- { id: "entrepreneurship", label: "entrepreneurship" },
- { id: "music", label: "music" },
- { id: "social event", label: "social" },
- { id: "athletics", label: "sports" },
- { id: "performing arts", label: "performing arts" },
- { id: "culture", label: "culture" },
- { id: "literature", label: "literature" },
- { id: "community service", label: "service" },
- { id: "religion", label: "religion" },
- { id: "politics", label: "politics" },
- { id: "gaming", label: "gaming" },
- { id: "outdoors", label: "outdoors" },
- { id: "sustainability", label: "sustainability" },
- { id: "speaker event", label: "speaker" },
-];
+ { id: "free-food", label: "Free Food" },
+ { id: "career-recruiting", label: "Career & Recruiting" },
+ { id: "research", label: "Research" },
+ { id: "stem", label: "STEM" },
+ { id: "academics", label: "Academics" },
+ { id: "tech", label: "Tech" },
+ { id: "entrepreneurship", label: "Entrepreneurship" },
+ { id: "politics-policy", label: "Politics & Policy" },
+ { id: "visual-arts", label: "Visual Arts" },
+ { id: "performing-arts", label: "Performing Arts" },
+ { id: "literature", label: "Literature" },
+ { id: "culture", label: "Culture" },
+ { id: "music", label: "Music" },
+ { id: "gaming", label: "Gaming" },
+ { id: "athletics", label: "Athletics" },
+ { id: "religion", label: "Religion" },
+ { id: "sustainability", label: "Sustainability" },
+ { id: "outdoor-adventure", label: "Outdoor & Adventure" },
+ { id: "wellness-self-care", label: "Wellness & Self-Care" },
+ { id: "community-service", label: "Community Service" },
+ { id: "speaker-event", label: "Speaker Event" },
+ { id: "social-event", label: "Social Event" },
+] as const;
+
+const LEGACY_INTEREST_ALIASES: Record = {
+ career: "career-recruiting",
+ careerrecruiting: "career-recruiting",
+ academic: "research",
+ academics: "academics",
+ tech: "tech",
+ political: "politics-policy",
+ politics: "politics-policy",
+ art: "visual-arts",
+ visual: "visual-arts",
+ performance: "performing-arts",
+ performing: "performing-arts",
+ cultural: "culture",
+ culture: "culture",
+ sports: "athletics",
+ athletic: "athletics",
+ religious: "religion",
+ religion: "religion",
+ outdoor: "outdoor-adventure",
+ outdoors: "outdoor-adventure",
+ wellness: "wellness-self-care",
+ speaker: "speaker-event",
+ social: "social-event",
+ "free-food": "free-food",
+ "free food": "free-food",
+ "community-service": "community-service",
+ "community service": "community-service",
+ "politics-policy": "politics-policy",
+ "politics policy": "politics-policy",
+ "visual-arts": "visual-arts",
+ "visual arts": "visual-arts",
+ "performing-arts": "performing-arts",
+ "performing arts": "performing-arts",
+ "wellness-self-care": "wellness-self-care",
+ "wellness self care": "wellness-self-care",
+ "speaker-event": "speaker-event",
+ "speaker event": "speaker-event",
+ "social-event": "social-event",
+ "social event": "social-event",
+ stem: "stem",
+};
+
+function normalizeInterestValue(tag: string): string {
+ const normalized = tag.trim().toLowerCase().replace(/\s+/g, "-");
+ return LEGACY_INTEREST_ALIASES[normalized] ?? normalized;
+}
+
+function dedupeInterestValues(values: string[]): string[] {
+ return [...new Set(values.map((value) => normalizeInterestValue(value)).filter(Boolean))];
+}
const CLASS_YEARS = ["2025", "2026", "2027", "2028", "2029", "Grad"];
@@ -82,7 +133,7 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
const [isPending, startTransition] = useTransition();
const initialClassYear = profile.classYear ?? "";
const initialMajor = profile.major ?? "";
- const initialInterests = profile.interests;
+ const initialInterests = dedupeInterestValues(profile.interests);
const initialAvatar = profile.avatarUrl;
const [classYear, setClassYear] = useState(initialClassYear);
@@ -94,7 +145,10 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
const [tagSearch, setTagSearch] = useState("");
const toggleInterest = (id: string) => {
- setInterests((prev) => (prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]));
+ setInterests((prev) => {
+ const next = prev.includes(id) ? prev.filter((interest) => interest !== id) : [...prev, id];
+ return dedupeInterestValues(next);
+ });
};
const interestsChanged =
@@ -110,13 +164,15 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
const handleSave = () => {
startTransition(async () => {
try {
+ const sanitizedInterests = dedupeInterestValues(interests);
await updateProfile({
classYear,
major,
isOrgLeader,
- interests,
+ interests: sanitizedInterests,
regions: profile.regions,
});
+ setInterests(sanitizedInterests);
toast.success("Settings saved");
} catch {
toast.error("Could not save settings. Please try again.");
From 84c23b3fc04f02dd080c0b885429511cbeebcf5f Mon Sep 17 00:00:00 2001
From: JiyaS12 <120071069+JiyaS12@users.noreply.github.com>
Date: Wed, 26 Aug 2026 10:15:35 -0500
Subject: [PATCH 3/3] style: format settings interests form
---
apps/web/src/app/(app)/settings/settings-client.tsx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/app/(app)/settings/settings-client.tsx b/apps/web/src/app/(app)/settings/settings-client.tsx
index c98e98b..00000d4 100644
--- a/apps/web/src/app/(app)/settings/settings-client.tsx
+++ b/apps/web/src/app/(app)/settings/settings-client.tsx
@@ -245,7 +245,12 @@ export function SettingsClient({ profile, friends }: SettingsClientProps) {
>
Cancel
-
+
{isPending ? "Saving…" : "Save changes"}