Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ jobs:
bench --site buzz.test set-config allow_tests true
bench --site buzz.test set-config host_name "http://buzz.test:8000"

- name: Create Test User
working-directory: /home/runner/frappe-bench
run: bench --site buzz.test add-user testuser@buzz.test --first-name Test --last-name User --password Test@123 --add-role "System Manager"

- name: Start Frappe Server
working-directory: /home/runner/frappe-bench
run: |
Expand All @@ -135,8 +139,8 @@ jobs:
run: npx playwright test
env:
BASE_URL: http://buzz.test:8000
FRAPPE_USER: Administrator
FRAPPE_PASSWORD: admin
FRAPPE_USER: testuser@buzz.test
FRAPPE_PASSWORD: Test@123

- name: Upload Playwright Report
uses: actions/upload-artifact@v4
Expand Down
38 changes: 38 additions & 0 deletions buzz/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import frappe
from frappe.utils import cint, md_to_html
from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys


@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def get_login_context(redirect_to: str | None = None):
context = {
"disable_signup": frappe.get_website_settings("disable_signup"),
"disable_user_pass_login": frappe.get_system_settings("disable_user_pass_login"),
"login_with_email_link": frappe.get_system_settings("login_with_email_link"),
"login_banner": md_to_html(raw_banner)
if (raw_banner := frappe.db.get_single_value("Buzz Settings", "login_banner"))
else None,
"provider_logins": [],
}

if not redirect_to:
redirect_to = frappe.utils.get_url("/dashboard")

social_login_keys = frappe.get_all(
"Social Login Key",
filters={"enable_social_login": 1},
fields=["name", "provider_name", "icon", "client_id", "base_url"],
)

for provider in social_login_keys:
if provider.client_id and provider.base_url and get_oauth_keys(provider.name):
context["provider_logins"].append(
{
"name": provider.name,
"provider_name": provider.provider_name,
"icon": provider.icon or "",
"auth_url": get_oauth2_authorize_url(provider.name, redirect_to),
}
)

return context
21 changes: 20 additions & 1 deletion buzz/events/doctype/buzz_settings/buzz_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"allow_add_ons_change_before_event_start_days",
"column_break_hagy",
"allow_ticket_cancellation_request_before_event_start_days",
"login_tab",
"login_banner_section",
"login_banner",
"communications_tab",
"ticketing_emails_section",
"default_ticket_email_template",
Expand Down Expand Up @@ -73,6 +76,22 @@
"label": "Support Email",
"options": "Email"
},
{
"fieldname": "login_tab",
"fieldtype": "Tab Break",
"label": "Login"
},
{
"fieldname": "login_banner_section",
"fieldtype": "Section Break",
"label": "Login Banner Config"
},
{
"description": "Promotional message shown in the login/signup modal. Supports Markdown.",
"fieldname": "login_banner",
"fieldtype": "Markdown Editor",
"label": "Login Banner"
},
{
"fieldname": "communications_tab",
"fieldtype": "Tab Break",
Expand Down Expand Up @@ -148,7 +167,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-01-13 22:34:24.493305",
"modified": "2026-03-26 17:39:40.326309",
"modified_by": "Administrator",
"module": "Events",
"name": "Buzz Settings",
Expand Down
3 changes: 2 additions & 1 deletion buzz/events/doctype/buzz_settings/buzz_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ class BuzzSettings(Document):
auto_send_pitch_deck: DF.Check
default_sponsor_deck_cc: DF.SmallText | None
default_sponsor_deck_email_template: DF.Link | None
default_sponsor_deck_reply_to: DF.Data
default_sponsor_deck_reply_to: DF.Data | None
default_ticket_email_template: DF.Link | None
login_banner: DF.MarkdownEditor | None
support_email: DF.Data | None
# end: auto-generated types

Expand Down
1 change: 1 addition & 0 deletions dashboard/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ declare module 'vue' {
EventSelector: typeof import('./src/components/EventSelector.vue')['default']
EventSponsorForm: typeof import('./src/components/EventSponsorForm.vue')['default']
LanguageSwitcher: typeof import('./src/components/LanguageSwitcher.vue')['default']
LoginDialog: typeof import('./src/components/LoginDialog.vue')['default']
LoginRequired: typeof import('./src/components/LoginRequired.vue')['default']
Navbar: typeof import('./src/components/Navbar.vue')['default']
OfflinePaymentDialog: typeof import('./src/components/OfflinePaymentDialog.vue')['default']
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import LoginDialog from "@/components/LoginDialog.vue";
import { FrappeUIProvider, setConfig } from "frappe-ui";
import Layout from "./layouts/Layout.vue";

Expand All @@ -11,5 +12,6 @@ setConfig("localTimezone", window.timezone?.user || null);
<Layout>
<router-view />
</Layout>
<LoginDialog />
</FrappeUIProvider>
</template>
56 changes: 35 additions & 21 deletions dashboard/src/components/BookingForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
<p class="text-xs text-ink-gray-5">
{{ __("Want to manage your bookings?") }}
</p>
<Button variant="outline" @click="redirectToLogin">
<Button variant="outline" @click="openLoginDialog()">
{{ __("Log in to your account") }}
</Button>
</div>
Expand Down Expand Up @@ -397,11 +397,12 @@

<script setup>
import { useBookingFormStorage } from "@/composables/useBookingFormStorage";
import { useLoginDialog } from "@/composables/useLoginDialog";
import { userResource } from "@/data/user";
import { formatCurrency, formatPriceOrFree } from "@/utils/currency";
import { clearBookingCache, redirectToLogin } from "@/utils/index";
import { clearBookingCache } from "@/utils/index";
import { FormControl, createResource, toast } from "frappe-ui";
import { computed, onUnmounted, ref, watch } from "vue";
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import LucideAlertCircle from "~icons/lucide/alert-circle";
import LucideCheck from "~icons/lucide/check";
Expand All @@ -417,6 +418,7 @@ import PaymentGatewayDialog from "./PaymentGatewayDialog.vue";

const router = useRouter();
const route = useRoute();
const { open: openLoginDialog } = useLoginDialog();

const getUtmParameters = () => {
const utmParams = [];
Expand Down Expand Up @@ -802,26 +804,25 @@ const totalCurrency = computed(() => {
return firstTicket ? firstTicket.currency : "INR";
});

// --- WATCHER ---
// Initialize with one attendee when component mounts (only if no data in storage)
watch(
() => props.availableTicketTypes,
() => {
if (attendees.value.length === 0 && props.availableTicketTypes.length > 0) {
const newAttendee = createNewAttendee();

// Pre-populate with current user's information if available
if (userResource.data) {
newAttendee.first_name = userResource.data.first_name || "";
newAttendee.last_name = userResource.data.last_name || "";
newAttendee.email = userResource.data.email || "";
}

attendees.value.push(newAttendee);
onMounted(async () => {
await nextTick();
if (attendees.value.length === 0 && props.availableTicketTypes.length > 0) {
const newAttendee = createNewAttendee();

if (guestFirstName.value || guestEmail.value) {
newAttendee.first_name = guestFirstName.value;
newAttendee.last_name = guestLastName.value;
newAttendee.email = guestEmail.value;
} else if (userResource.data) {
newAttendee.first_name = userResource.data.first_name || "";
newAttendee.last_name = userResource.data.last_name || "";
newAttendee.email = userResource.data.email || "";
}
},
{ immediate: true }
);

attendees.value = [newAttendee];
}
});

// Ensure existing attendees have proper add-on structure when availableAddOns changes
watch(
Expand Down Expand Up @@ -1073,6 +1074,11 @@ const validateForm = () => {
async function submit() {
if (processBooking.loading) return;

if (requiresLogin.value) {
openLoginDialog();
return;
}

// Validate mandatory fields
const validationErrors = validateForm();
if (validationErrors.length > 0) {
Expand Down Expand Up @@ -1357,11 +1363,19 @@ function clearOtpState() {

const isWebinar = computed(() => props.eventDetails.category === "Webinars");

const requiresLogin = computed(() => {
return props.isGuestMode && !props.eventDetails?.allow_guest_booking;
});

const submitButtonText = computed(() => {
if (processBooking.loading) {
return __("Processing...");
}

if (requiresLogin.value) {
return __("Book Tickets");
}

if (finalTotal.value > 0) {
return __("Pay & Book");
}
Expand Down
Loading
Loading