Skip to content

feat(profile): add My Workshops page for booked schedule items#4660

Merged
estyxx merged 1 commit into
mainfrom
feature/profile-my-workshops
May 29, 2026
Merged

feat(profile): add My Workshops page for booked schedule items#4660
estyxx merged 1 commit into
mainfrom
feature/profile-my-workshops

Conversation

@estyxx
Copy link
Copy Markdown
Member

@estyxx estyxx commented May 28, 2026

Summary

  • Add bookedScheduleItems(conference) on the GraphQL User type so logged-in attendees can list workshops they booked (only items with a schedule slot, so start/end resolve safely).
  • Add a My Workshops entry on the profile dashboard and a /profile/my-workshops page showing title, date/time, room, and a link to each event.
  • Add English and Italian copy for the new section and empty state.
image image

Test plan

  • Log in as a user with at least one workshop booking and open Profile → My Workshops; confirm booked workshops appear with correct date/time and room.
  • Open My Workshops with no bookings; confirm empty state and link to the schedule.
  • Confirm workshops booked but not yet assigned a schedule slot do not appear (and do not error).
  • Run docker exec pycon-backend-1 uv run pytest api/users/tests/test_booked_schedule_items.py

Users could book workshops but had no profile view of their bookings.
Expose bookedScheduleItems on User and add a profile section listing
title, time, room, and a link to each event (scheduled items only).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pycon Error Error May 28, 2026 3:14pm

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 28, 2026

Adds a "My Workshops" profile page that lists a user's booked schedule items, with a new bookedScheduleItems GraphQL resolver, frontend components, and i18n strings.

Critical: Unsafe destructuring crashes on load

In frontend/src/components/my-workshops-profile-page-handler/index.tsx:

const {
  data: {
    me: { bookedScheduleItems },
  },
} = useMyProfileWithBookedWorkshopsQuery({ ... })

data is undefined during the loading state, so this throws immediately on render before the query resolves. Should use optional chaining/null coalescing instead:

const { data } = useMyProfileWithBookedWorkshopsQuery({ ... })
const bookedScheduleItems = data?.me?.bookedScheduleItems ?? []

Error handling in getServerSideProps is too broad

The catch block in frontend/src/pages/profile/my-workshops.tsx redirects to /login on any error (network failures, 500s, etc.). Only auth errors should redirect to login; other failures should show an error page or propagate.

Reusing profile.myProposals.date i18n key for workshops

my-workshops-table.tsx reuses the proposals date format key instead of the new workshop-specific keys added in the same PR. If proposal and workshop date formats ever diverge, this implicit coupling will be hard to find.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.52%. Comparing base (5be0ac3) to head (3cafa88).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4660   +/-   ##
=======================================
  Coverage   92.52%   92.52%           
=======================================
  Files         359      359           
  Lines       10791    10797    +6     
  Branches      821      821           
=======================================
+ Hits         9984     9990    +6     
  Misses        696      696           
  Partials      111      111           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@estyxx
Copy link
Copy Markdown
Member Author

estyxx commented May 29, 2026

@claude do the code review again

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 29, 2026

Claude finished @estyxx's task in 1m 24s —— View job


Code Review Complete

  • Read changed files and understand the implementation
  • Review backend GraphQL resolver and tests
  • Review frontend components and page
  • Check for bugs, security issues, and best practices
  • Post comprehensive review findings

Summary

This PR adds a "My Workshops" profile page that lists a user's booked schedule items. The implementation includes a new bookedScheduleItems GraphQL resolver, frontend components, and i18n strings for English and Italian.

What looks good

Backend (well-implemented):

  • The bookedScheduleItems resolver correctly filters to only return items with a slot (slot__isnull=False), preventing errors when start/end are accessed
  • Good use of select_related and prefetch_related for query optimization
  • Proper ordering by day and hour
  • Comprehensive test coverage: tests for happy path, exclusion of unscheduled items, user isolation, and auth requirement

Frontend:

  • Component structure follows existing patterns (my-tickets, my-grant, etc.)
  • i18n strings added for both English and Italian
  • Dashboard link properly added with consistent styling

Issues to address

1. Reusing proposals date format key (Minor)

my-workshops-table.tsx:62 uses profile.myProposals.date instead of a workshop-specific key:

<FormattedMessage
  id="profile.myProposals.date"  // Should use a workshop-specific key
  values={{...}}
/>

While this works since both use the same format, if proposal and workshop date formats ever need to diverge, this implicit coupling will be hard to find. Consider adding profile.myWorkshops.date for consistency with the other workshop-specific keys you've added.

Fix this →


Patterns matching existing code (informational)

These patterns match how other profile pages work but are worth noting:

Direct destructuring without loading state

The component destructures data directly:

const {
  data: {
    me: { bookedScheduleItems },
  },
} = useMyProfileWithBookedWorkshopsQuery({...})

This works because getServerSideProps prefetches the data into the Apollo cache before rendering. Other profile pages (my-tickets, my-grant, my-orders) use the same pattern, so this is consistent with the codebase.

Catch-all redirect to login

The getServerSideProps redirects to /login on any error, not just auth errors. This is the same pattern used by my-tickets.tsx, my-grant.tsx, and other profile pages, so it's consistent with existing code.


Overall

The implementation is solid and follows established patterns in the codebase. The backend work is particularly well done with good query optimization and comprehensive tests. The minor i18n key reuse is the only actionable item.

@estyxx
Copy link
Copy Markdown
Member Author

estyxx commented May 29, 2026

@claude do the final code review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 29, 2026

Claude finished @estyxx's task in 1m 13s —— View job


Final Code Review

  • Read all changed files and understand the implementation
  • Review backend GraphQL resolver and types
  • Review backend tests for completeness
  • Review frontend components and pages
  • Review i18n strings
  • Check for bugs, security issues, and best practices
  • Post comprehensive review findings

Summary

This PR adds a "My Workshops" profile page showing booked schedule items. The implementation is well done and follows established patterns in the codebase.

Backend ✅

backend/api/users/types.py:117-131 - The bookedScheduleItems resolver is solid:

  • Correctly filters slot__isnull=False to prevent errors when accessing start/end
  • Good query optimization with select_related("slot", "slot__day", "language") and prefetch_related for rooms
  • Proper ordering by slot__day__day, slot__hour
  • Uses IsAuthenticated permission class

backend/api/users/tests/test_booked_schedule_items.py - Comprehensive test coverage:

  • ✅ Happy path (booked items returned correctly)
  • ✅ Excludes items without a slot
  • ✅ User isolation (doesn't leak other users' bookings)
  • ✅ Authentication required

Frontend ✅

The implementation closely follows existing patterns from my-tickets, my-grant, and other profile pages:

  • Component structure: MyWorkshopsProfilePageHandler, MyWorkshopsTable, NoWorkshops match the established pattern
  • SSR prefetch: getServerSideProps correctly prefetches query data into Apollo cache
  • Data destructuring: The direct destructuring in index.tsx:12-20 works safely because data is prefetched server-side (same pattern as my-tickets-profile-page-handler)
  • Error handling: Catch-all redirect to /login matches other profile pages

i18n ✅

All translations present for both English and Italian:

  • profile.myWorkshops.title
  • profile.myWorkshops.noWorkshops.heading/.body
  • profile.myWorkshops.browseSchedule
  • profile.myWorkshops.viewWorkshop
  • profile.myWorkshops.room

One Minor Improvement (Optional)

my-workshops-table.tsx:62 - Still reuses profile.myProposals.date:

<FormattedMessage
  id="profile.myProposals.date"  // Could be "profile.myWorkshops.date"
  values={{...}}
/>

This works since both use the same format {day} {start} - {end}, but adding a dedicated key would improve maintainability. This is optional and doesn't block merge.

Fix this →


Verdict

Approved ✅ - Clean implementation that follows codebase conventions. Backend has proper filtering and good test coverage. Ready to merge.


@estyxx estyxx merged commit 9a2e69c into main May 29, 2026
7 of 9 checks passed
@estyxx estyxx deleted the feature/profile-my-workshops branch May 29, 2026 07:19
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.

1 participant