Skip to content
Open
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
31 changes: 20 additions & 11 deletions dashboard/src/components/dashboard/events/TicketTypeDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import type { TicketType } from "@/types";
import { Button, Dialog, FormControl, toast } from "frappe-ui";
import { computed, ref, watch } from "vue";

// The list on the page owns the insert, so creating a tier refreshes it for free.
// The list on the page owns the write, so a saved tier refreshes it for free. A `tier`
// means we are editing that one; without it the dialog creates.
const props = defineProps<{
event: string;
insert: (doc: Partial<TicketType> & Record<string, unknown>) => Promise<unknown>;
tier?: TicketType | null;
save: (doc: Partial<TicketType> & Record<string, unknown>) => Promise<unknown>;
}>();
const isOpen = defineModel<boolean>({ required: true });

Expand All @@ -19,6 +21,7 @@ const title = ref("");
const price = ref("");
const currency = ref("INR");
const capacity = ref("");
const published = ref(true);
const showErrors = ref(false);
const saving = ref(false);
const failure = ref("");
Expand All @@ -35,10 +38,13 @@ const problem = computed(() => {

watch(isOpen, (open) => {
if (!open) return;
title.value = "";
price.value = "";
currency.value = "INR";
capacity.value = "";
title.value = props.tier?.title ?? "";
price.value = props.tier ? String(props.tier.price) : "";
currency.value = props.tier?.currency ?? "INR";
capacity.value = props.tier?.max_tickets_available
? String(props.tier.max_tickets_available)
: "";
published.value = props.tier ? Boolean(props.tier.is_published) : true;
showErrors.value = false;
failure.value = "";
});
Expand All @@ -50,11 +56,12 @@ async function submit() {
saving.value = true;
failure.value = "";
try {
await props.insert({
event: props.event,
await props.save({
...(props.tier ? { name: props.tier.name } : { event: props.event }),
title: title.value.trim(),
price: Number(price.value),
currency: currency.value,
is_published: published.value,
// The doctype spells unlimited as 0.
max_tickets_available: capacity.value === "" ? 0 : Number(capacity.value),
});
Expand All @@ -65,13 +72,13 @@ async function submit() {
saving.value = false;
}

toast.success(`${title.value.trim()} added`);
toast.success(`${title.value.trim()} ${props.tier ? "updated" : "added"}`);
isOpen.value = false;
}
</script>

<template>
<Dialog v-model="isOpen" title="New ticket type">
<Dialog v-model="isOpen" :title="tier ? 'Edit ticket type' : 'New ticket type'">
<form novalidate class="space-y-4" @submit.prevent="submit">
<FormControl
v-model="title"
Expand Down Expand Up @@ -106,6 +113,8 @@ async function submit() {
placeholder="Unlimited"
/>

<FormControl v-model="published" type="checkbox" label="Published" />

<p v-if="showErrors && problem" class="text-sm text-ink-red-4">{{ problem }}</p>
<ErrorMessage v-else-if="failure" :message="failure" />

Expand All @@ -115,7 +124,7 @@ async function submit() {
<Button
type="button"
variant="solid"
label="Create ticket type"
:label="tier ? 'Save changes' : 'Create ticket type'"
class="w-full"
:loading="saving"
@click="submit"
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/data/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function useMyTickets() {
export function useEventTicketTypes(event: string) {
return useList<TicketType>({
doctype: "Event Ticket Type",
fields: ["name", "title", "price", "currency", "max_tickets_available"],
fields: ["name", "title", "price", "currency", "is_published", "max_tickets_available"],
filters: { event },
orderBy: "creation asc",
limit: 0,
Expand Down
57 changes: 40 additions & 17 deletions dashboard/src/pages/manage/events/EventRegistrations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import GuestRegistrationDialog from "@/components/dashboard/events/GuestRegistra
import TicketTypeDialog from "@/components/dashboard/events/TicketTypeDialog.vue";
import { eventDetail } from "@/data/events";
import { useEventTicketTypes } from "@/data/tickets";
import type { TicketType } from "@/types";
import { formatPriceOrFree } from "@/utils/currency";
import { Button, LoadingText } from "frappe-ui";
import { Badge, Button, LoadingText } from "frappe-ui";
import { ref } from "vue";
import { useRoute } from "vue-router";

Expand All @@ -19,7 +20,18 @@ const ticketTypes = useEventTicketTypes(eventId);

const editingRegistration = ref(false);
const editingGuestRegistration = ref(false);
const addingTicketType = ref(false);
const editingTicketType = ref(false);
// Null while creating; the tier being edited otherwise.
const ticketTypeInEdit = ref<TicketType | null>(null);

function editTicketType(ticketType: TicketType | null) {
ticketTypeInEdit.value = ticketType;
editingTicketType.value = true;
}

function saveTicketType(doc: Partial<TicketType> & Record<string, unknown>) {
return doc.name ? ticketTypes.setValue.submit(doc) : ticketTypes.insert.submit(doc);
}
</script>

<template>
Expand Down Expand Up @@ -48,7 +60,7 @@ const addingTicketType = ref(false);
variant="subtle"
icon-left="plus"
label="New Ticket Type"
@click="addingTicketType = true"
@click="editTicketType(null)"
/>
</div>

Expand All @@ -57,19 +69,29 @@ const addingTicketType = ref(false);
<ul
v-else-if="ticketTypes.data?.length"
aria-label="Ticket types"
class="divide-y divide-outline-gray-1 overflow-hidden rounded-xl border border-outline-gray-2"
class="grid grid-cols-2 gap-3"
>
<li
v-for="ticketType in ticketTypes.data"
:key="ticketType.name"
class="flex items-baseline gap-2 px-4 py-3"
>
<span class="text-base font-medium text-ink-gray-9">{{
ticketType.title
}}</span>
<span class="text-base text-ink-gray-5">
{{ formatPriceOrFree(ticketType.price, ticketType.currency) }}
</span>
<li v-for="ticketType in ticketTypes.data" :key="ticketType.name">
<button
type="button"
class="w-full space-y-2 rounded-xl border border-outline-gray-2 bg-surface-white p-4 text-left transition-colors hover:border-outline-gray-3"
@click="editTicketType(ticketType)"
>
<Badge
:theme="ticketType.is_published ? 'green' : 'gray'"
variant="subtle"
size="sm"
>
{{ ticketType.is_published ? __("Available") : __("Sale Ended") }}
</Badge>

<p class="text-lg font-medium text-ink-gray-9">{{ ticketType.title }}</p>

<span class="flex items-center gap-1.5 text-sm text-ink-gray-6">
<span class="lucide-banknote size-4" aria-hidden="true" />
{{ formatPriceOrFree(ticketType.price, ticketType.currency) }}
</span>
</button>
</li>
</ul>

Expand All @@ -96,8 +118,9 @@ const addingTicketType = ref(false);
/>

<TicketTypeDialog
v-model="addingTicketType"
v-model="editingTicketType"
:event="eventId"
:insert="ticketTypes.insert.submit"
:tier="ticketTypeInEdit"
:save="saveTicketType"
/>
</template>
1 change: 1 addition & 0 deletions dashboard/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface TicketType {
title: string
price: number
currency: string
is_published: boolean
max_tickets_available: number
}

Expand Down
23 changes: 23 additions & 0 deletions e2e/tests/manage-event.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
await expect(page.getByRole("heading", { name: "Guest list" })).toBeVisible();
// The shared event is the one tickets.setup.ts books against, so it has guests.
const rows = page.getByRole("list").getByRole("listitem");
await expect(rows.first()).toBeVisible({ timeout: 15000 });

Check failure on line 82 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests

1) [chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests ─ Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 80 | // The shared event is the one tickets.setup.ts books against, so it has guests. 81 | const rows = page.getByRole("list").getByRole("listitem"); > 82 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 83 | 84 | // The count is the number of rows under it, not a separate claim. 85 | const registrations = Number(await page.getByText(/^\d+$/).first().textContent()); at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:82:30

Check failure on line 82 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests

1) [chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests ─ Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 80 | // The shared event is the one tickets.setup.ts books against, so it has guests. 81 | const rows = page.getByRole("list").getByRole("listitem"); > 82 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 83 | 84 | // The count is the number of rows under it, not a separate claim. 85 | const registrations = Number(await page.getByText(/^\d+$/).first().textContent()); at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:82:30

Check failure on line 82 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests

1) [chromium] › e2e/tests/manage-event.spec.ts:75:6 › Event workspace › lists the event's guests ─ Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 80 | // The shared event is the one tickets.setup.ts books against, so it has guests. 81 | const rows = page.getByRole("list").getByRole("listitem"); > 82 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 83 | 84 | // The count is the number of rows under it, not a separate claim. 85 | const registrations = Number(await page.getByText(/^\d+$/).first().textContent()); at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:82:30

// The count is the number of rows under it, not a separate claim.
const registrations = Number(await page.getByText(/^\d+$/).first().textContent());
Expand All @@ -96,7 +96,7 @@
await page.getByRole("link", { name: "Guests" }).click();

const rows = page.getByRole("list").getByRole("listitem");
await expect(rows.first()).toBeVisible({ timeout: 15000 });

Check failure on line 99 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search

2) [chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 97 | 98 | const rows = page.getByRole("list").getByRole("listitem"); > 99 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 100 | const everyone = await rows.count(); 101 | 102 | // The email of whoever happens to be first, so this holds on any site. Read from at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:99:30

Check failure on line 99 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search

2) [chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 97 | 98 | const rows = page.getByRole("list").getByRole("listitem"); > 99 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 100 | const everyone = await rows.count(); 101 | 102 | // The email of whoever happens to be first, so this holds on any site. Read from at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:99:30

Check failure on line 99 in e2e/tests/manage-event.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search

2) [chromium] › e2e/tests/manage-event.spec.ts:95:6 › Event workspace › narrows the guest list by search Error: expect(locator).toBeVisible() failed Locator: getByRole('list').getByRole('listitem').first() Expected: visible Timeout: 15000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 15000ms - waiting for getByRole('list').getByRole('listitem').first() 97 | 98 | const rows = page.getByRole("list").getByRole("listitem"); > 99 | await expect(rows.first()).toBeVisible({ timeout: 15000 }); | ^ 100 | const everyone = await rows.count(); 101 | 102 | // The email of whoever happens to be first, so this holds on any site. Read from at /home/runner/work/buzz/buzz/e2e/tests/manage-event.spec.ts:99:30
const everyone = await rows.count();

// The email of whoever happens to be first, so this holds on any site. Read from
Expand Down Expand Up @@ -203,6 +203,29 @@
await expect(row).toContainText("499");
});

test("edits a ticket type from its card", async ({ page }) => {
await page.getByRole("link", { name: "Registrations" }).click();

const title = `Late Bird ${Date.now()}`;
await page.getByRole("button", { name: "New Ticket Type" }).click();
await page.getByRole("textbox", { name: "Title" }).fill(title);
await page.getByRole("spinbutton", { name: "Price" }).fill("100");
await page.getByRole("button", { name: "Create ticket type" }).click();

const card = page
.getByRole("list", { name: "Ticket types" })
.getByRole("listitem")
.filter({ hasText: title });
await expect(card).toBeVisible({ timeout: 15000 });

await card.getByRole("button").click();
await page.getByRole("spinbutton", { name: "Price" }).fill("250");
await page.getByRole("button", { name: "Save changes" }).click();

await expect(page.getByText(`${title} updated`)).toBeVisible();
await expect(card).toContainText("250");
});

test("moves between sections", async ({ page }) => {
await page.getByRole("link", { name: "Talks" }).click();

Expand Down
Loading