Skip to content

feat: community calendars with add and submit events - #553

Merged
harshtandiya merged 27 commits into
developfrom
feat/team-communities-rename
Oct 9, 2026
Merged

harshtandiya merged 27 commits into
developfrom
feat/team-communities-rename

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Teams become communities. Each community gets a calendar of its own events plus the events it features, and organisers review what others submit.

  • Rename: teams → communities, is_a_community → accept_community_submissions, URLs move to /manage/communities.
  • Calendar: at /manage/communities/:id/calendar (old /events links redirect). Lists own + approved Buzz and external events (teams.get_team_events). Pending submissions show above it as a compact list, newest first, with Approve / Reject. Reject asks for an optional reason, which goes into the email. A Buzz event opens in the event drawer, with Reject and Approve at its foot; an external one opens its link. Long lists load more on scroll.
  • Add Event: beside the Upcoming / Past toggle (on mobile, the floating button opens it full width). Create New, Add Existing from Buzz (paste a link), or Add External (link, name, place, host, dates).
  • Public page: the same menu mounts as a Vue island. Organisers add directly, signed-in users submit for review (managers get an email), guests get the login dialog.
  • Emails: the submitter gets a confirmation when the event is sent, then an approved or not-approved email. Organizers get a review request with a link to the calendar. Replies from the submitter go to the community's support email, else its owner; replies from organizers go to the submitter. Administrator and Guest are never emailed.
  • Islands: a second Vite build mounts dashboard components on Jinja pages.

Screenshots

Community calendar Mobile: Add Event menu
calendar mobile
Add External Public community page
external public
Email: to submitter Email: to organizers
received submitted

Testing

  • buzz.api.communities.test_communities: 29 tests pass.
  • Checked by hand: dashboard calendar, and the public page as organiser, non-member and guest.

Follow-up: #552 (login dialog as an island instead of an iframe).

harshtandiya and others added 13 commits October 8, 2026 18:52
…t_community_submissions

Dashboard nav and the /manage/teams page now read Communities and show one list.
The flag only marks a team that accepts event submissions, so it no longer
drives grouping or the workspace subtitle. A patch keeps existing values.

Buzz Team form: publishing (published, route, slug), description (short
description, about) and links each get their own section on the Public Page tab.
Old /manage/teams links redirect to the same path under /manage/communities.
…kspace

Clicking a community card covers the main panel with "Opening <name>" and
shows skeleton rows in the sidebar until the workspace's events have loaded,
for at least one second. The centred shimmer line is shared with Create
Event's progress state.
…meline tabs

Every community page header ends with a Community Page button on desktop,
disabled until the community is published. On a community's events page,
Create Event moves next to the Upcoming/Past toggle.

The mobile back button now targets the renamed communities route.
A Submissions item in the community workspace lists pending and approved
event submissions from other teams. Editors approve, reject or remove them.
A community that accepts submissions shows a Submit event button. Guests are
sent to log in; signed-in visitors pick one of their upcoming events in a
dialog and submit it for review.
The dialog is being rebuilt in Vue, shared by the dashboard and the public page.
…lic pages

A second Vite build mounts dashboard Vue components (frappe-ui included) on
Jinja pages. A page loads a small entry script; Vue and the component load on
the first click of the island's trigger. frappe-ui's styles stay scoped to the
island. `yarn build`, and so `bench build`, runs it.

Public pages also get the dashboard's `lucide-*` icon classes, built from
frappe-ui's Lucide plugin with only the icons the templates use.
Callers pass a lucide-* name instead of filling the illustration slot with the same Icon markup. The slot stays for richer illustrations.
The community workspace's Events item becomes Calendar, and the Submissions
page goes. Pending submissions from other teams sit at the top of the
Calendar, each with its date, time, place and submitter, and Approve /
Remove. Below them, the Events list keeps its Upcoming / Past tabs, with a
create button beside the heading.
A request can now be for an event hosted on another platform, such as Luma
or KonfHub. With Is External Event on, the request holds the event's name,
host, location (with Google place and coordinates), start and end, and an
optional URL, and needs no Buzz Event. The controller checks those fields,
since the form's mandatory_depends_on is not enforced on the server.

The community's queue takes an external request's date, time, place and
host from the request itself, and its card links to the event URL.
…nity calendar

- Add a welcome Alert with a solid calendar icon above the community calendar
- Retitle the queue "Pending Approval" with a count badge and clearer copy
- Make Approve subtle and Remove ghost, and disable one while the other runs
- Animate a reviewed row out and slide the rest up
- Drop the Create Event button from the Events heading

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The community calendar gets an Add Event menu with three ways in:
create a new event, add an existing Buzz event from its link, or add
an event hosted on another platform. Create sends the organiser back to
the calendar after it saves.

The public community page shows the same menu as a Vue island. Its
organisers see Add Event and add straight away. Any other signed-in
user sees Submit Event and sends the event for review. A guest gets
the login dialog.

- get_team_events lists a team's own events, plus the Buzz and external
  events its community approved, each flagged is_community_request and
  is_external. The dashboard calendar now uses it.
- An external event card opens its link in a new tab and shows an
  External badge.
- find_event, add_external_event and submit_external_event are new.
  submit_external_event is rate limited, since it emails the managers.
- An external event needs all of link, name, location, host and dates,
  and its link must be http or https.
- Islands can mount in place of their trigger (data-island-inline),
  and render toasts.
- useCall().submit() does not reject on a server error, so the review
  and add forms now check .error. A failed action no longer reports
  success.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

✅ UI Demo Check — a screenshot or demo is attached.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Backend test coverage: 91%

Commit e4c79e4. Test modules are excluded.

Coverage by file
Name Stmts Miss Cover
buzz/__init__.py 5 5 0%
buzz/api/account/__init__.py 30 2 93%
buzz/api/account/exceptions.py 8 0 100%
buzz/api/account/schemas.py 4 0 100%
buzz/api/account/services.py 15 1 93%
buzz/api/auth/__init__.py 11 0 100%
buzz/api/auth/schemas.py 3 0 100%
buzz/api/booking/__init__.py 30 1 97%
buzz/api/booking/coupons.py 30 3 90%
buzz/api/booking/details.py 93 7 92%
buzz/api/booking/event_data.py 42 4 90%
buzz/api/booking/exceptions.py 21 0 100%
buzz/api/booking/guests.py 71 10 86%
buzz/api/booking/my_bookings.py 9 3 67%
buzz/api/booking/schemas.py 38 0 100%
buzz/api/booking/services.py 212 19 91%
buzz/api/campaigns/__init__.py 34 19 44%
buzz/api/campaigns/exceptions.py 11 0 100%
buzz/api/campaigns/schemas.py 2 0 100%
buzz/api/checkin/__init__.py 9 0 100%
buzz/api/checkin/exceptions.py 14 0 100%
buzz/api/checkin/schemas.py 8 0 100%
buzz/api/checkin/services.py 62 1 98%
buzz/api/communications/__init__.py 16 0 100%
buzz/api/communications/exceptions.py 11 0 100%
buzz/api/communications/schemas.py 6 0 100%
buzz/api/communications/services.py 65 1 98%
buzz/api/communities/__init__.py 49 0 100%
buzz/api/communities/exceptions.py 14 0 100%
buzz/api/communities/notifications.py 34 2 94%
buzz/api/communities/queries.py 36 0 100%
buzz/api/communities/reviews.py 39 1 97%
buzz/api/communities/schemas.py 13 0 100%
buzz/api/communities/submissions.py 54 1 98%
buzz/api/events/__init__.py 49 0 100%
buzz/api/events/exceptions.py 20 0 100%
buzz/api/events/schemas.py 85 0 100%
buzz/api/events/services.py 221 13 94%
buzz/api/events/taxes.py 19 0 100%
buzz/api/events/ticket_types.py 43 0 100%
buzz/api/events/zoom.py 15 1 93%
buzz/api/exceptions.py 23 0 100%
buzz/api/filters/conditions.py 99 2 98%
buzz/api/filters/exceptions.py 5 0 100%
buzz/api/filters/schemas.py 6 0 100%
buzz/api/forms/__init__.py 24 0 100%
buzz/api/forms/answers.py 77 14 82%
buzz/api/forms/exceptions.py 27 0 100%
buzz/api/forms/fields.py 82 3 96%
buzz/api/forms/schemas.py 7 0 100%
buzz/api/forms/services.py 119 1 99%
buzz/api/maps/__init__.py 16 0 100%
buzz/api/maps/exceptions.py 11 0 100%
buzz/api/maps/schemas.py 9 0 100%
buzz/api/maps/services.py 57 0 100%
buzz/api/payments/__init__.py 10 1 90%
buzz/api/payments/currencies.py 7 2 71%
buzz/api/payments/schemas.py 2 0 100%
buzz/api/proposals/__init__.py 18 0 100%
buzz/api/proposals/exceptions.py 5 0 100%
buzz/api/proposals/schemas.py 26 0 100%
buzz/api/proposals/services.py 104 1 99%
buzz/api/schemas.py 13 0 100%
buzz/api/sponsorships/__init__.py 37 0 100%
buzz/api/sponsorships/enquiries.py 74 2 97%
buzz/api/sponsorships/exceptions.py 22 0 100%
buzz/api/sponsorships/forms.py 64 2 97%
buzz/api/sponsorships/manage.py 21 1 95%
buzz/api/sponsorships/schemas.py 18 0 100%
buzz/api/sponsorships/services.py 67 1 99%
buzz/api/teams/__init__.py 47 2 96%
buzz/api/teams/calendar.py 28 0 100%
buzz/api/teams/exceptions.py 24 0 100%
buzz/api/teams/invitations.py 34 0 100%
buzz/api/teams/public_page.py 26 15 42%
buzz/api/teams/schemas.py 14 0 100%
buzz/api/teams/services.py 100 1 99%
buzz/api/tickets/__init__.py 15 0 100%
buzz/api/tickets/emails.py 14 9 36%
buzz/api/tickets/exceptions.py 29 0 100%
buzz/api/tickets/schemas.py 4 0 100%
buzz/api/tickets/services.py 85 0 100%
buzz/api/tickets/windows.py 12 0 100%
buzz/buzz/doctype/buzz_custom_field/buzz_custom_field.py 20 4 80%
buzz/buzz/web_form/apply_for_sponsorship/apply_for_sponsorship.py 3 3 0%
buzz/buzz/web_form/propose_a_talk/propose_a_talk.py 3 3 0%
buzz/buzz_marketing/doctype/buzz_campaign/buzz_campaign.py 21 7 67%
buzz/emails.py 29 3 90%
buzz/events/banner_pattern.py 35 0 100%
buzz/events/doctype/additional_event_page/additional_event_page.py 15 1 93%
buzz/events/doctype/buzz_event/buzz_event.py 267 23 91%
buzz/events/doctype/buzz_event_form/buzz_event_form.py 11 4 64%
buzz/events/doctype/buzz_form_field/buzz_form_field.py 3 0 100%
buzz/events/doctype/buzz_price/buzz_price.py 10 0 100%
buzz/events/doctype/buzz_settings/buzz_settings.py 13 2 85%
buzz/events/doctype/buzz_team/buzz_team.py 75 3 96%
buzz/events/doctype/buzz_team/team_map.py 23 0 100%
buzz/events/doctype/buzz_team/team_page.py 74 2 97%
buzz/events/doctype/buzz_team_membership/buzz_team_membership.py 59 0 100%
buzz/events/doctype/buzz_team_settings/buzz_team_settings.py 44 0 100%
buzz/events/doctype/buzz_theme/buzz_theme.py 42 2 95%
buzz/events/doctype/buzz_theme/tokens.py 29 1 97%
buzz/events/doctype/buzz_theme_token/buzz_theme_token.py 3 0 100%
buzz/events/doctype/community_event_request/community_event_request.py 56 12 79%
buzz/events/doctype/event_category/event_category.py 9 0 100%
buzz/events/doctype/event_check_in/event_check_in.py 10 1 90%
buzz/events/doctype/event_cohost/event_cohost.py 4 0 100%
buzz/events/doctype/event_communication/event_communication.py 16 0 100%
buzz/events/doctype/event_external_link/event_external_link.py 4 0 100%
buzz/events/doctype/event_featured_speaker/event_featured_speaker.py 4 0 100%
buzz/events/doctype/event_feedback/event_feedback.py 4 4 0%
buzz/events/doctype/event_host/event_host.py 4 0 100%
buzz/events/doctype/event_payment_gateway/event_payment_gateway.py 4 0 100%
buzz/events/doctype/event_sponsor/event_sponsor.py 15 3 80%
buzz/events/doctype/event_talk/event_talk.py 8 0 100%
buzz/events/doctype/event_template/event_template.py 38 4 89%
buzz/events/doctype/event_template_add_on/event_template_add_on.py 4 0 100%
buzz/events/doctype/event_template_custom_field/event_template_custom_field.py 4 0 100%
buzz/events/doctype/event_template_ticket_type/event_template_ticket_type.py 4 0 100%
buzz/events/doctype/event_track/event_track.py 4 4 0%
buzz/events/doctype/event_user_preferences/event_user_preferences.py 4 4 0%
buzz/events/doctype/event_venue/event_venue.py 53 1 98%
buzz/events/doctype/event_venue/geocode.py 69 9 87%
buzz/events/doctype/event_venue/map_link.py 106 5 95%
buzz/events/doctype/offline_payment_method/offline_payment_method.py 11 0 100%
buzz/events/doctype/schedule_item/schedule_item.py 4 0 100%
buzz/events/doctype/social_media_link/social_media_link.py 3 0 100%
buzz/events/doctype/speaker_profile/speaker_profile.py 8 1 88%
buzz/events/doctype/sponsor_enquiry_form/sponsor_enquiry_form.py 54 5 91%
buzz/events/doctype/sponsorship_tier/sponsorship_tier.py 27 1 96%
buzz/events/doctype/talk_speaker/talk_speaker.py 3 0 100%
buzz/events/doctype/utm_parameter/utm_parameter.py 4 0 100%
buzz/events/og_image.py 141 7 95%
buzz/events/og_text.py 66 1 98%
buzz/events/online_meeting.py 42 1 98%
buzz/events/report/event_attendance_summary/event_attendance_summary.py 61 61 0%
buzz/events/report/event_overview/event_overview.py 25 25 0%
buzz/hooks.py 28 0 100%
buzz/install.py 60 28 53%
buzz/integrations/zoom.py 3 3 0%
buzz/islands.py 10 5 50%
buzz/payments.py 96 33 66%
buzz/permissions.py 137 12 91%
buzz/proposals/doctype/event_proposal/event_proposal.py 71 5 93%
buzz/proposals/doctype/proposal_speaker/proposal_speaker.py 4 0 100%
buzz/proposals/doctype/sponsorship_deck_item/sponsorship_deck_item.py 4 0 100%
buzz/proposals/doctype/sponsorship_enquiry/sponsorship_enquiry.py 81 5 94%
buzz/proposals/doctype/talk_proposal/talk_proposal.py 92 6 93%
buzz/proposals/doctype/talk_proposal_status/talk_proposal_status.py 4 0 100%
buzz/tasks.py 4 4 0%
buzz/telemetry.py 46 0 100%
buzz/telemetry_scan.py 55 8 85%
buzz/ticketing/doctype/additional_field/additional_field.py 4 0 100%
buzz/ticketing/doctype/attendee_ticket_add_on/attendee_ticket_add_on.py 3 0 100%
buzz/ticketing/doctype/buzz_coupon_code/buzz_coupon_code.py 99 4 96%
buzz/ticketing/doctype/coupon_free_add_on/coupon_free_add_on.py 3 0 100%
buzz/ticketing/doctype/event_booking/event_booking.py 355 28 92%
buzz/ticketing/doctype/event_booking_attendee/event_booking_attendee.py 20 7 65%
buzz/ticketing/doctype/event_booking_refund/event_booking_refund.py 56 0 100%
buzz/ticketing/doctype/event_booking_refund_ticket/event_booking_refund_ticket.py 4 0 100%
buzz/ticketing/doctype/event_payment/event_payment.py 4 0 100%
buzz/ticketing/doctype/event_ticket/event_ticket.py 80 10 88%
buzz/ticketing/doctype/event_ticket_type/event_ticket_type.py 69 0 100%
buzz/ticketing/doctype/ticket_add_on/ticket_add_on.py 9 1 89%
buzz/ticketing/doctype/ticket_add_on_value/ticket_add_on_value.py 3 0 100%
buzz/ticketing/doctype/ticket_cancellation_item/ticket_cancellation_item.py 4 0 100%
buzz/ticketing/doctype/ticket_cancellation_request/ticket_cancellation_request.py 19 1 95%
buzz/ticketing/report/detailed_event_registrations/detailed_event_registrations.py 111 5 95%
buzz/ticketing/report/event_add_ons_overview/event_add_ons_overview.py 20 20 0%
buzz/uninstall.py 10 2 80%
buzz/utils.py 138 22 84%
buzz/www/dashboard.py 24 3 88%
buzz/www/event/date_range.py 42 16 62%
buzz/www/event/index.py 140 8 94%
buzz/www/event/link_icons.py 1 0 100%
buzz/www/event/meta.py 65 6 91%
buzz/www/event/venue_map.py 35 1 97%
buzz/www/events.py 69 6 91%
buzz/www/site_header.py 25 0 100%
TOTAL 6792 607 91%

89 empty files skipped.

harshtandiya and others added 2 commits October 9, 2026 11:57
Frappe's semgrep file-traversal rule blocks a raw open() in app code.
The manifest path is a constant, so frappe.utils.get_file_json reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dynamic <component is="button"> resolves to frappe-ui's globally
registered Button, so the full-card overlay rendered as a grey 28px pill
over the top of every card. Use static <a> and <button> tags instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

harshtandiya and others added 6 commits October 9, 2026 12:16
TimelineList gets an actions slot after the tabs, and the community
calendar fills it with the Add Event menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irst

Pending submissions render as one bordered list instead of a stack of
cards. Each row shows the event's banner, or a link tile for an external
event with an External badge and its domain, and the submitter with how
long ago they submitted. Approve and Remove are tinted icon buttons.
A Buzz event opens in the event drawer; an external one opens its link.

get_requests now returns each request's creation and the event's banner,
and lists pending requests newest first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shared event link with tracking parameters runs past 140 characters,
so event_url now holds 500. Name, host and location stay at 140, and
the API and the form both refuse longer values instead of failing on
the database write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pending rows clamp the title to two lines, cap the submitter column
  and carry the submitter on narrow screens, with full values on hover.
- A pending list scrolls in its own box and renders more rows as it
  scrolls; the calendar loads more events on scroll instead of Show more.
- A missing banner falls back to the pattern; tap targets grow on mobile.
- The floating button sits above list actions, and for organisers it
  opens the Add Event menu.
- Event cards hyphenate long titles and clamp the venue on mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The floating button sits in a full-width row that lets taps through
everywhere but the button. As the menu trigger, that row gives the menu
the screen's width, right-aligned with the button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full-width mobile menu sizes its icons and labels so each row is as
tall as the large button that opens it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

The page is called Calendar, so its route is too. Old /events links
redirect to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

harshtandiya and others added 2 commits October 9, 2026 12:57
The submitter now gets an email when their event reaches a community,
saying the organizers will review it and they will hear back.

The community emails also show what was submitted: the event header for
a Buzz event, a details card with the link for an external one. The
organizers' email gets a Review submission button, and each email says
why the reader received it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A submitter's reply reaches the community: its support email, else its
owner. An organizer's reply to a new submission reaches the submitter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

harshtandiya and others added 3 commits October 9, 2026 13:15
They have no inbox, so a community managed by Administrator, or a
request it submitted, only produced failed emails. The sender drops them
from recipients and reply-to, and skips the email when nobody is left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… drawer

Rejecting a submission opens a dialog for an optional reason, which goes
to the submitter in the rejection email. The event drawer of a pending
Buzz event gets Reject and Approve buttons at its foot.

The row and the drawer share one review composable; the row's Remove
is now Reject, which is what it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya
harshtandiya merged commit 0cddb04 into develop Oct 9, 2026
10 checks passed
@harshtandiya
harshtandiya deleted the feat/team-communities-rename branch October 9, 2026 08:17
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