Skip to content

Code cleanup#158

Merged
Ascent817 merged 18 commits intomainfrom
code-cleanup
Feb 23, 2026
Merged

Code cleanup#158
Ascent817 merged 18 commits intomainfrom
code-cleanup

Conversation

@Ascent817
Copy link
Member

Description

Cleaned up code that modifies the DOM imperatively rather than using React best practices. Also reduced code duplication and used map to generate tag filters rather than the 12 hardcoded ones we had before.

@github-actions
Copy link

github-actions bot commented Feb 20, 2026

Visit the preview URL for this PR (updated for commit e320c92):

https://magnify--pr158-code-cleanup-tz5nhp0a.web.app

(expires Mon, 02 Mar 2026 00:53:11 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 9d7af0ca9f95ffb1f7aaef8a25560cf9e42173b6

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the codebase to eliminate imperative DOM manipulation and embrace React best practices. The changes centralize application logic in App.tsx with proper state management, extract Firebase configuration to a dedicated module, and use the modern createRoot API for rendering.

Changes:

  • Migrated from imperative DOM manipulation to React state management for search, tag filtering, and user authentication
  • Consolidated Firebase initialization into a single config module and removed redundant initialization code
  • Refactored TagBar component to use map() instead of 16 hardcoded tag buttons

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/index.tsx Simplified to use React 18's createRoot API and removed all business logic
src/App.tsx Now manages all application state (courseData, user, authLevel, search, tags) with React hooks instead of global variables and DOM manipulation
src/components/TopBar.tsx Converted to controlled component with searchQuery state and event handlers passed as props
src/components/TagBar.tsx Refactored to generate tag buttons via map() and receive state/handlers as props
src/components/ClearFilter.tsx Updated to use props for callbacks and useEffect for side effects
src/components/Course.tsx Simplified to use centralized Firebase db export instead of initializing Firebase locally
src/config/firebase.ts Centralized Firebase initialization and exports auth, db, and provider instances
src/config/index.ts Added exports for auth, db, and provider
src/utils/getCookieValue.ts New utility extracted for cookie parsing
src/utils/index.ts Added export for getCookieValue utility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +130 to +138
const courseIDtoNameMap = useMemo(() => {
const map = new Map<string, string>();
for (const courseName in courseData) {
courseData![courseName].courseid.match(COURSE_ID_REGEX)?.forEach((id) => {
map.set(id, courseName);
});
}
return map;
}, [courseData]);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The courseIDtoNameMap computation will crash when courseData is null. The useMemo checks if courseData exists in the loop condition but uses the non-null assertion operator, which can cause a runtime error if courseData is null during the initial render before the useEffect sets it.

Add a guard check at the beginning of the useMemo: if (!courseData) return new Map();

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ascent817 is coursedata gonna ever be null, don't we load in the pre-downloaded coursedata json regardless

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should always have local to fall back on. We should make sure to update the local version when we update the db.

snapshot.val().users;
Object.values(users).forEach(({ email, level }) => {
if (user.email === email) setAuthLevel(level);
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth level fetch effect lacks error handling. If the database read fails, the error will be unhandled. Add a .catch() handler to the promise chain to handle potential database errors gracefully.

Suggested change
});
});
})
.catch((error) => {
console.error('Failed to fetch auth level:', error);

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +29
useEffect(() => {
const courseContainer = document.getElementById(
'course-container'
) as HTMLDivElement;

const clearResults = () => {
const search = document.getElementById('searchbar') as HTMLInputElement;
search.value = '';

const tags = document.getElementsByClassName('tag');
for (let i = 0; i < tags.length; i++) {
if (tags[i].classList.contains('tag-true')) {
tags[i].classList.remove('tag-true');
}
}
const adjustHeight = () => {
courseContainer.style.height =
getWidth() >= 525
? 'calc(100vh - 180px)'
: 'calc(100vh - (45.5px + 65.5px + (clamp(15px, 5vw, 20px) * 2)))';
};

filterCourses();
};
adjustHeight();
window.addEventListener('resize', adjustHeight);
return () => {
window.removeEventListener('resize', adjustHeight);
courseContainer.style.height = '';
};
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ClearFilter component directly manipulates the DOM to set the height of the course-container element. This goes against the PR's stated goal of eliminating imperative DOM manipulation in favor of React best practices. Consider managing the container height through React state and inline styles on the container div in App.tsx, similar to how display and justifyContent are already handled on lines 262-266.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ascent817 well well well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well this could be fixed if we used CSS subgrid

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but thats a LOT of work

Comment on lines +100 to +120
useEffect(() => {
let prevScrollPos = window.scrollY;
const handleScroll = () => {
const topButton = document.getElementById('to-top');
if (topButton) {
const visible = window.scrollY >= 80 && windowWidth >= 525;
topButton.style.visibility = visible ? 'visible' : 'hidden';
topButton.style.opacity = visible ? '1' : '0';
}
if (windowWidth < 525) {
const nav = document.getElementById('mobile-nav');
if (nav) {
nav.style.bottom =
window.scrollY < prevScrollPos ? '0' : `-${nav.offsetHeight}px`;
}
}
prevScrollPos = window.scrollY;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [windowWidth]);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scroll handler in App.tsx continues to use imperative DOM manipulation to control the visibility of the back-to-top button and mobile navigation. This is inconsistent with the PR's stated goal of using React best practices instead of imperative DOM manipulation. Consider refactoring to use React state to track scroll position and control visibility through conditional rendering or className props.

Copilot uses AI. Check for mistakes.
);
));
return <div className="parent">{flexParents}</div>;
}, [filteredCourseItems, numColumns, handleClearFilters]);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The classItems useMemo includes handleClearFilters in its dependency array, but handleClearFilters is stable (has no dependencies). This can cause unnecessary re-computation of the classItems layout when it doesn't need to change. However, since handleClearFilters is wrapped in useCallback with an empty dependency array, this is actually fine. Consider documenting this relationship or removing it from dependencies if handleClearFilters is guaranteed to be stable.

Suggested change
}, [filteredCourseItems, numColumns, handleClearFilters]);
}, [filteredCourseItems, numColumns]);

Copilot uses AI. Check for mistakes.
const signedInUser = result.user;
if (!signedInUser) return;
setUser(signedInUser);
document.cookie = `user=${JSON.stringify(signedInUser)}`;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the entire Firebase user object in a cookie is a security concern. The user object may contain sensitive information and tokens that should not be stored in cookies, which are vulnerable to XSS attacks and can be transmitted with every HTTP request. Consider using Firebase's built-in session persistence or storing only minimal, non-sensitive user identifiers in cookies (like UID), and relying on Firebase Auth's persistence mechanisms instead.

Suggested change
document.cookie = `user=${JSON.stringify(signedInUser)}`;
document.cookie = `user=${encodeURIComponent(signedInUser.uid)}`;

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +59
const [user, setUser] = useState<firebase.User | null>(() => {
const cookie = getCookieValue('user');
if (!cookie) return null;
try {
return JSON.parse(cookie);
} catch {
return null;
}
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user state initialization from cookies lacks validation. If the cookie contains a malformed or malicious JSON payload that successfully parses but doesn't match the expected Firebase User structure, it could cause runtime errors when the app tries to access user properties like user.email or user.photoURL. Consider adding type validation or using a schema validator to ensure the parsed object conforms to the expected Firebase User interface.

Copilot uses AI. Check for mistakes.
Comment on lines +190 to +202
auth.signOut().then(() => {
setUser(null);
setAuthLevel(0);
document.cookie =
'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
});
} else {
auth.signInWithPopup(provider).then((result) => {
const signedInUser = result.user;
if (!signedInUser) return;
setUser(signedInUser);
document.cookie = `user=${JSON.stringify(signedInUser)}`;
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authentication sign-in and sign-out promises lack error handling. If the Firebase authentication fails (network issues, popup blocked, etc.), the promise rejection will be unhandled, potentially causing user-facing errors or leaving the UI in an inconsistent state. Add .catch() handlers to both auth.signOut() and auth.signInWithPopup(provider) calls to handle errors gracefully.

Suggested change
auth.signOut().then(() => {
setUser(null);
setAuthLevel(0);
document.cookie =
'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
});
} else {
auth.signInWithPopup(provider).then((result) => {
const signedInUser = result.user;
if (!signedInUser) return;
setUser(signedInUser);
document.cookie = `user=${JSON.stringify(signedInUser)}`;
});
auth
.signOut()
.then(() => {
setUser(null);
setAuthLevel(0);
document.cookie =
'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
})
.catch((error) => {
console.error('Error signing out:', error);
});
} else {
auth
.signInWithPopup(provider)
.then((result) => {
const signedInUser = result.user;
if (!signedInUser) return;
setUser(signedInUser);
document.cookie = `user=${JSON.stringify(signedInUser)}`;
})
.catch((error) => {
console.error('Error signing in:', error);
});

Copilot uses AI. Check for mistakes.
Comment on lines 206 to 212
@@ -34,26 +211,62 @@
contactModal?.showModal();
}, []);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleContactModalOpen function uses imperative DOM manipulation to open the contact modal. This is inconsistent with the PR's goal of eliminating imperative DOM manipulation. Consider managing modal state through React state and passing it as a prop to the ContactForm component.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dang we still have a lot of technical debt...

@GoogolGenius
Copy link
Member

@Ascent817 I used the preview URL, and I cannot login. The popup gets blocked even though I allowed popups. Possible bug? Is it working on localhost?

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Comment on lines +12 to +29
useEffect(() => {
const courseContainer = document.getElementById(
'course-container'
) as HTMLDivElement;

const clearResults = () => {
const search = document.getElementById('searchbar') as HTMLInputElement;
search.value = '';

const tags = document.getElementsByClassName('tag');
for (let i = 0; i < tags.length; i++) {
if (tags[i].classList.contains('tag-true')) {
tags[i].classList.remove('tag-true');
}
}
const adjustHeight = () => {
courseContainer.style.height =
getWidth() >= 525
? 'calc(100vh - 180px)'
: 'calc(100vh - (45.5px + 65.5px + (clamp(15px, 5vw, 20px) * 2)))';
};

filterCourses();
};
adjustHeight();
window.addEventListener('resize', adjustHeight);
return () => {
window.removeEventListener('resize', adjustHeight);
courseContainer.style.height = '';
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ascent817 well well well

Comment on lines 206 to 212
@@ -34,26 +211,62 @@ const App: FC<AppProps> = ({ user, classItems, authLevel }): JSX.Element => {
contactModal?.showModal();
}, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dang we still have a lot of technical debt...

Comment on lines +130 to +138
const courseIDtoNameMap = useMemo(() => {
const map = new Map<string, string>();
for (const courseName in courseData) {
courseData![courseName].courseid.match(COURSE_ID_REGEX)?.forEach((id) => {
map.set(id, courseName);
});
}
return map;
}, [courseData]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ascent817 is coursedata gonna ever be null, don't we load in the pre-downloaded coursedata json regardless

@Ascent817
Copy link
Member Author

@Ascent817 I used the preview URL, and I cannot login. The popup gets blocked even though I allowed popups. Possible bug? Is it working on localhost?

It was working on localhost for me, maybe the preview URL isn't an authorized domain to send OAuth requests from?

@GoogolGenius
Copy link
Member

@Ascent817 I used the preview URL, and I cannot login. The popup gets blocked even though I allowed popups. Possible bug? Is it working on localhost?

It was working on localhost for me, maybe the preview URL isn't an authorized domain to send OAuth requests from?

That's probably what's happening

@Ascent817 Ascent817 merged commit ed781b4 into main Feb 23, 2026
5 checks passed
@Ascent817 Ascent817 deleted the code-cleanup branch February 23, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants