feat: section swap page#224
Conversation
Adds /swap route with a weekly calendar view of the user's schedule, section finder panel, and a Quest import overlay for users without a schedule. Includes ephemeral (unauthenticated) schedule support and a "Section swap" link in the profile dropdown. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2e1ce0e to
5e6f7a8
Compare
- Show enrollment as filled/capacity (e.g. 53/90) instead of "X of Y open" - Add Last Updated timestamp above section list - Conditionally link prof name to their page only if they exist in DB - Add updated_at field to swap course GQL query and SwapSection type - Add fontSize prop to LastUpdatedSchedule for flexible sizing - Add ProfNameText styled component for profs without a UWFlow profile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added align-items: center to the ContentWrapper styled component for improved layout consistency.
Stack steps vertically and shrink fixed-width upload/picture/video boxes on screens <= 800px so the /welcome onboarding fits on mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the deprecated split Apollo 2.x packages (apollo-client,
apollo-link-*, apollo-cache-inmemory, react-apollo, @apollo/react-*)
with the unified @apollo/client v3.14.1, and bump graphql 14 -> 16.
- Hooks / ApolloProvider / ApolloQueryResult now import from
@apollo/client; links from @apollo/client/link/{context,error}.
- Refactor the legacy <Query> render-prop in ProfileDropdown to useQuery
(render-prop components are removed in Apollo v4).
- Extract the InMemoryCache dataIdFromObject into a unit-testable module
and add a Jest suite covering every custom + default cache key.
- codegen.js gains reactApolloVersion: 3 so regenerated output uses
`import * as Apollo from '@apollo/client'`.
Webpack 4 build fix: graphql 16 ships untranspiled class fields, so add
@babel/plugin-proposal-class-properties to the node_modules babel pass.
Jest-on-modern-Node fixes: pin graceful-fs to 4.2.11 (jest 24's copy
crashes on Node >=20) and treat node_modules .cjs/.mjs as modules (not
assets) so @apollo/client's ESM-flagged package loads under Jest.
Stay on @apollo/client v3 (not v4) until the Vite/Webpack 5 migration:
v4 is ESM-only with an exports map Webpack 4 cannot resolve.
Verified: tsc --noEmit, bun run lint-nofix, bun run test (8/8) and a
full Webpack 4 production build all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the profile-page week grid into a presentational, domain-free <Calendar> at src/components/calendar, styled with Tailwind instead of styled-components. ProfileCalendar now owns the Moment/schedule math and maps each ScheduleInterval onto a CalendarEvent. The component exposes per-event `variant` (section colour) and `state` (default | selected | dimmed | preview), plus an `interactive` flag and a `header` slot, so the upcoming section-swap page can reuse it directly: preview a candidate section as a blurred dashed ghost, dim the events it would replace, and ring the selected class -- without re-implementing the grid. Removes the now-dead src/pages/profilePage/Calendar.tsx and its styles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolved conflicts preferring main, except ProfileCalendar.tsx imports, which were kept from the branch so the merged calendar-refactor body still compiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…taIdFromObject Address review comments on #246: - Move the week-navigation header (date range, hours-this-week, and the prev / next / current-week controls) into the generic Calendar via a showHeader toggle + header*/onPrevWeek/onNextWeek/onCurrentWeek props, replacing the custom `header` slot. Nav buttons reuse the shared Button styled to match input/Button (light, bordered, Anderson font). - Remove the unused dataIdFromObject.ts and its test; apollo.js already normalizes the cache via typePolicies, matching main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Changed button variant from "outline" to "default" for week navigation buttons to ensure consistent styling. - Updated the border style of the navigation button class to use 'border-solid' for better visual clarity.
…tton Remove classes verified to have no effect: box-border (global reset already sets border-box), h-full on flex-stretched day columns, the header's flex wrapper, and dead items-center/justify-center on the non-flex shared button. Fold transition-all into the truncate hover condition, replace the inline AndersonFont style with font-anderson, and switch nav buttons to the ghost variant so NAV_BUTTON_CLASS no longer overrides the default variant's colors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Migrate swapPage imports to @apollo/client (post-main-merge) - Drop COURSE_DROPDOWN_QUERY: the dropdown always has a term, so the term-scoped query is the only one needed (3 queries total now: sections by class numbers, sections by course code, course codes) - Convert SectionFinderPanel to Tailwind and delete its styled-components file, as the migration starting point - Revert DataUploadModals media-query additions to main's version Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
lazyRetry.ts and ErrorFallback.tsx were committed by accident; nothing imports them yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace non-null assertions with narrowed locals/guards, drop the unused refetchAll prop plumbing, and remove an unused mixin import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the hand-rolled styled-components week grid in SwapCalendar with components/calendar. Event cards now show a bold course code over a 'SECTION · LOCATION' line with variant-tinted fills, and the selected course gets the gold full-border treatment. Page title is now 'Swap classes' with the existing subtitle beneath it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports ScheduleSwapPanel from the schedule-swap-panel branch into the swap page, replacing SectionFinderPanel while keeping its Apollo data layer (sections, conflicts, prof ratings). The panel now owns the term tabs and the 'Swap X with Y' row; the swap target still uses the server-backed CourseSearchDropdown, anchored under the panel's select-styled trigger. 'Switch section' pins the chosen section as a persistent calendar preview (no enrollment mutation exists). Section-row previews use pointer events (onPointerEnter/Leave) so they work with pen and touch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Uppercase muted day labels, 24-hour gutter times, fainter half-hour lines, saturated accent left rails over pastel fills, and centered three-line event content (course code lg, time · location, section sm). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses PR review: deletes both swapPage styles files - SwapPage's wrapper/overlay/import-card and CourseSearchDropdown's dropdown styles are converted to Tailwind, and the FadeInWrapper is replaced with the animate-fade-in utility. The shared Button's border-none moves out of the cva base into an @layer base button reset in src/index.css, standing in for the disabled preflight (audited: no button relies on the browser default border). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removes the export button and its secretId/handleExport plumbing. The slot is empty until the user makes a temporary swap, at which point the reset button appears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses PR review: COURSE_SECTION_FIELDS becomes a real gql fragment (SwapCourseSection) and bun run generate emits the operation and fragment types (purely additive, no schema drift). All hand-written types in SwapCourse.tsx are deleted; consumers import generated types. The fragment is a structural superset of the schedule entry's section, so the temporary-swap bridge no longer needs a cast, and dead null guards on non-null columns are gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stacked on #262 (merge that first). Second of three PRs matching the swap page to the approved mockups. - **Logged out** → the calendar renders a hardcoded, non-interactive sample week (MATH 239, CS 240, STAT 231, CS 241, FINE 100 — negative section ids, dated today so it always lands in the current term tab) behind the existing frosted overlay, now containing a lock card: grey lock circle, "**Upload your schedule to swap**", "Log in and paste your courses from Quest to start swapping sections.", and a yellow **Log in to continue** button that opens `AUTH_MODAL` via `useModal`. - **Logged in without a schedule** → unchanged Quest-paste upload card. - `SwapCalendar` gains a `demoMode` prop that disables block clicks; the course-select trigger and section finder are already inert with no selection, and Export stays hidden (no `secretId`). - The logged-out "ephemeral parse" flow (`GET_SECTIONS_BY_CLASS_NUMBERS`) lost its only entry point under this design and has now been removed entirely (the linter flagged it as dead code). Verified with eslint (clean for `src/pages/swapPage`) and `tsc --noEmit` (only the pre-existing `json-stable-stringify` config error). No live-browser verification — please check `/swap` logged-out locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Can we align all the border radius with the rest of the app, move reset button inside the top section and remove the default button outline? Also why is there an "X" button on the bottom panel? Doesn't seem like we should be able to close it Color for "Conflicts" should probably match our error text which is more red? Why is it so close to the courses orange color The action button for changing sections should be yellow like the rest of the CTAs in the app, and maybe "choose section" instead of "switch section"? The dropdown for course selection is also not consistent with rest of the app, should only have orange text without the outline Also not sure if I like the "Tag" format for the lecture name (Lec 001, etc.), should that just be the same title text format (bold + dark)? And probably just hide "Instructor TBA" entirely if there's no instructor, i thought we don't get that data anymore |
- Align all border radii to the app-standard 4px (rounded) - Move Reset inside the top swap bar as a borderless control - Remove the close (X) button from the section panel; re-clicking the calendar block still deselects - "Conflicts" label and 0-seat count use darkRed instead of red - Section CTA is now the accent-yellow "Choose section" (new accent variant on ui/button) - Course dropdown trigger is plain orange text, no box or focus ring - Section names render as bold dark text instead of a tag badge - Hide the instructor line entirely when there is no instructor data Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed in f94a123:
|
Replace the hand-rolled `button { border: none }` stand-in with
Preflight's `border-width: 0; border-style: solid`, so the `border`
utility alone draws the outline variant and no `border-solid` workaround
is needed. Addresses review feedback to move the button reset onto a
Tailwind preflight basis (full Preflight stays disabled to coexist with
the styled-components app).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rmat
- Center AnnouncementBanner content (add justify-center, drop flex-1 so the
icon/message/CTA/dismiss pack and center as a group)
- EmptyState placeholder icon RefreshCw -> MousePointer to match the
"Click a class in your schedule" copy
- Add font-inter to the swap-bar dropdown trigger and Reset button: with
Preflight disabled, public/index.css forces font-family: sans-serif on
<button>, so the trigger rendered in a different font than its Inter
siblings ("not aligned properly")
- Course dropdown rows render codes via formatCourseCode ("CS 135" not
"CS135"), matching the rest of the app
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Direct/refreshed requests to client-side routes (e.g. /swap) had no matching file or rewrite and returned 404. Add a catch-all rewrite to /index.html, after the /graphql and /api proxies so those still win. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture term-schedule (Quest paste) upload failures via Sentry.captureException in the UPLOAD_FAILED branch, tagged feature: schedule_upload. Forwards HTTP status, the backend error enum, any unmatched class numbers, and the exact pasted schedule text so parse failures can be reproduced. Also read the pasted value into a local instead of the stale scheduleText state so the correct text is sent on each attempt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fadeIn keyframe was opacity-only over 0.2s, much subtler than the app's <FadeIn> (react-fade-in), which animates opacity 0->1 plus a 20px upward rise over 400ms. Match it: add the translateY(20px) rise and bump the duration to 0.4s ease. Move the fade off the SwapPage wrapper onto the SwapCalendar content (which already uses animate-fade-in). A transform on the wrapper would re-anchor the always-mounted fixed login/upload overlay, so the wrapper keeps a plain background and the content carries the fade, mirroring how the app's other pages wrap content in <FadeIn>. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Section Swap experience at /swap, providing a weekly schedule view with an interactive section browser (conflict detection, seat counts, prof ratings) and a course swap comparison workflow. This also advances the Tailwind migration by implementing the page UI in Tailwind and enhancing shared calendar styling to support the new UX.
Changes:
- Introduces the
/swaproute and new swap page components (calendar view, section panel, course search dropdown). - Adds GraphQL queries/fragments to fetch swap-related section data and a course list per term.
- Updates shared UI (Calendar styling/labels, announcement banner, profile dropdown nav) and Tailwind/base CSS to support the new page.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| vercel.json | Adds SPA rewrite fallback so /swap works on direct navigation. |
| tailwind.config.js | Updates fadeIn keyframes/animation timing to match legacy page transitions. |
| src/types/Api.tsx | Adds parse-only schedule response types for upload callback usage. |
| src/Routes.tsx | Defines SWAP_PAGE_ROUTE. |
| src/pages/welcomePage/styles/WelcomePage.tsx | Adds mobile padding adjustments on Welcome page wrapper. |
| src/pages/swapPage/SwapPage.tsx | New /swap page: auth/schedule gating + upload/login overlay behavior. |
| src/pages/swapPage/SwapCalendar.tsx | New swap calendar container: term switching, conflict detection, preview + temporary swap state. |
| src/pages/swapPage/ScheduleSwapPanel.tsx | New section list panel: conflicts, seats, prof ratings, preview interactions, “choose section”. |
| src/pages/swapPage/demoSchedule.ts | Demo schedule data for logged-out view. |
| src/pages/swapPage/CourseSearchDropdown.tsx | New dropdown for searching courses to swap against (fuzzy search + virtualized list). |
| src/pages/profilePage/ProfileCalendar.tsx | Aligns profile calendar time/day labels and link styling with swap page calendar. |
| src/LoadableComponents.tsx | Adds lazy-loaded SwapPage entry. |
| src/index.css | Adds minimal @layer base button border reset since Tailwind preflight is disabled. |
| src/graphql/queries/course/SwapCourse.tsx | Adds swap-related fragments and queries (sections by course, sections by class #, course list by term). |
| src/generated/graphql.tsx | Regenerates types/hooks for new swap queries/fragments. |
| src/components/upload/ScheduleUploadModalContent.tsx | Enhances upload callback typing and adds Sentry reporting on upload failures. |
| src/components/ui/button.tsx | Adds accent variant and documents border reset dependency. |
| src/components/navigation/ProfileDropdown.tsx | Adds “Section swap” nav option to profile dropdown. |
| src/components/common/styles/LastUpdatedSchedule.tsx | Adds optional fontSize styling support + inherit link font-size. |
| src/components/common/LastUpdatedSchedule.tsx | Wires fontSize prop through to styled wrapper. |
| src/components/calendar/Calendar.tsx | Updates calendar visuals (header height, labels, event rendering/states) to support swap UX. |
| src/components/banner/AnnouncementBanner.tsx | Updates banner content to promote the new swapper and improves dismissal keying. |
| src/App.tsx | Registers /swap route via SentryRoute + loadable SwapPage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| extra: { | ||
| status, | ||
| error: (response as ErrorResponse).error, | ||
| failedClasses: (response as ScheduleParseResponse).failed_classes, | ||
| // The exact text the user pasted, so we can reproduce parse failures. | ||
| schedule: pastedSchedule, | ||
| }, |
| role={clickable ? 'button' : undefined} | ||
| tabIndex={clickable ? 0 : undefined} | ||
| onClick={clickable ? event.onClick : undefined} | ||
| style={{ top, height }} |
| useEffect(() => { | ||
| inputRef.current?.focus(); | ||
| }, []); |
| const isEnrolled = false; | ||
| return ( | ||
| <button | ||
| type="button" | ||
| style={style} | ||
| className={cn( | ||
| 'flex w-full cursor-pointer items-center border-0 border-b border-solid border-light2 px-3.5 py-2.5 text-left text-[13px] text-dark1 last:border-b-0 hover:bg-light1', | ||
| isSelected | ||
| ? 'bg-[#eef4ff]' | ||
| : isEnrolled | ||
| ? 'bg-[#f5f8ff]' | ||
| : 'bg-transparent', | ||
| )} | ||
| onClick={() => onSelect(course.code)} | ||
| > |
| const startHour = startSeconds / 3600; | ||
| if (startHour < GRID_START_HOUR || startHour > GRID_END_HOUR) return []; | ||
| return days.map((d) => DAY_LETTERS.indexOf(d)).filter((col) => col !== -1); |
| padding: 16px; | ||
| padding-top: 16px; | ||
| box-sizing: border-box; |
Visit it on the
/swapendpoint in the vercel previewRelies on UWFlow/uwflow#184Summary
Adds a
/swappage where you can view your weekly schedule and swap individual course sections without juggling Quest tabs./swaprouteState handling depending on auth/schedule:
Built entirely in Tailwind (no styled-components) so it can serve as the starting point for the migration.
Test plan