add a checkbox for free events

This commit is contained in:
2024-06-06 01:32:36 +02:00
parent 980e191c9f
commit a3c8095ed0
8 changed files with 103 additions and 32 deletions

View File

@ -0,0 +1,21 @@
function registerPricingPanelToggler() {
const checkbox = document.querySelector(
`input[type="checkbox"][id$="id_free"]`
);
const specificPricingPanel = document.querySelector(
`div#specific_pricing_panel`
);
function onChange() {
specificPricingPanel.style.display = checkbox.checked ? "none" : "block";
}
if (checkbox && specificPricingPanel) {
checkbox.addEventListener("change", () => onChange());
onChange();
}
}
document.addEventListener("DOMContentLoaded", () => {
registerPricingPanelToggler();
});

View File

@ -1,5 +1,7 @@
from django.contrib.admin.utils import quote from django.contrib.admin.utils import quote
from django.templatetags.static import static
from django.urls import reverse from django.urls import reverse
from django.utils.html import format_html
from wagtail import hooks from wagtail import hooks
from wagtail.admin.menu import MenuItem from wagtail.admin.menu import MenuItem
@ -47,3 +49,8 @@ def make_publish_default_action(menu_items, request, context):
menu_items.pop(index) menu_items.pop(index)
menu_items.insert(0, item) menu_items.insert(0, item)
break break
@hooks.register("insert_editor_js")
def editor_js():
return format_html('<script src="{}"></script>', static("js/page-editor.js"))

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-06-05 22:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0028_eventpage_pig_alter_eventpage_body'),
]
operations = [
migrations.AddField(
model_name='eventpage',
name='free',
field=models.BooleanField(default=False),
),
]

View File

@ -192,12 +192,15 @@ class EventPage(Page):
help_text="Lenke direkte til arrangementet på Facebook", help_text="Lenke direkte til arrangementet på Facebook",
) )
free = models.BooleanField(null=False, default=False)
price_regular = models.IntegerField(null=True, blank=True) price_regular = models.IntegerField(null=True, blank=True)
price_student = models.IntegerField(null=True, blank=True) price_student = models.IntegerField(null=True, blank=True)
price_member = models.IntegerField(null=True, blank=True) price_member = models.IntegerField(null=True, blank=True)
ticket_panels = [ ticket_panels = [
FieldPanel("ticket_url", heading="Billettkjøpslenke"), FieldPanel("free", heading="Gratis", help_text="Er dette arrangementet gratis for alle?"),
MultiFieldPanel(
children=[
FieldRowPanel( FieldRowPanel(
children=[ children=[
FieldPanel("price_regular", heading="Ordinær pris"), FieldPanel("price_regular", heading="Ordinær pris"),
@ -207,6 +210,10 @@ class EventPage(Page):
help_text="", help_text="",
), ),
HelpPanel(content="La alle prisfeltene stå tomme om arrangementet er gratis."), HelpPanel(content="La alle prisfeltene stå tomme om arrangementet er gratis."),
],
attrs={"id": "specific_pricing_panel"},
),
FieldPanel("ticket_url", heading="Billettkjøpslenke"),
] ]
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
@ -220,10 +227,7 @@ class EventPage(Page):
heading="Facebook-lenke", heading="Facebook-lenke",
help_text="Lenke direkte til arrangementet på Facebook.", help_text="Lenke direkte til arrangementet på Facebook.",
), ),
MultiFieldPanel( MultiFieldPanel(heading="Priser og billettkjøp", children=ticket_panels),
heading="Priser og billettkjøp",
children=ticket_panels,
),
MultiFieldPanel( MultiFieldPanel(
children=[ children=[
HelpPanel( HelpPanel(
@ -233,7 +237,7 @@ class EventPage(Page):
"legg inn hver dag som en egen forekomst." "legg inn hver dag som en egen forekomst."
), ),
), ),
InlinePanel("occurrences", min_num=1), InlinePanel("occurrences", min_num=1, label="Forekomst"),
] ]
), ),
] ]
@ -244,6 +248,7 @@ class EventPage(Page):
GraphQLString("pig"), GraphQLString("pig"),
GraphQLString("ticket_url"), GraphQLString("ticket_url"),
GraphQLString("facebook_url"), GraphQLString("facebook_url"),
GraphQLBoolean("free"),
GraphQLInt("price_regular"), GraphQLInt("price_regular"),
GraphQLInt("price_student"), GraphQLInt("price_student"),
GraphQLInt("price_member"), GraphQLInt("price_member"),
@ -270,6 +275,15 @@ class EventPage(Page):
), ),
] ]
def clean(self):
super().clean()
# if the event is free, all specific pricing is unset
if self.free:
self.price_regular = None
self.price_student = None
self.price_member = None
class EventOccurrence(Orderable): class EventOccurrence(Orderable):
event = ParentalKey(EventPage, on_delete=models.CASCADE, related_name="occurrences") event = ParentalKey(EventPage, on_delete=models.CASCADE, related_name="occurrences")

View File

@ -4,6 +4,17 @@ import Image from "@/components/general/Image";
import { Pig } from "../general/Pig"; import { Pig } from "../general/Pig";
import Link from "next/link"; import Link from "next/link";
function formatPrice(price: number): string {
if (price === null) {
// should not happen
return "?";
}
if (price === 0) {
return "Gratis";
}
return `${price} kr`;
}
export const EventHeader = ({ event }: { event: EventFragment }) => { export const EventHeader = ({ event }: { event: EventFragment }) => {
const featuredImage: any = event.featuredImage; const featuredImage: any = event.featuredImage;
@ -19,29 +30,27 @@ export const EventHeader = ({ event }: { event: EventFragment }) => {
<div className={styles.prices}> <div className={styles.prices}>
{/*<h2>Pris</h2>*/} {/*<h2>Pris</h2>*/}
<ul className={styles.priceList}> <ul className={styles.priceList}>
{!event.priceRegular && {event.free && (
!event.priceStudent &&
!event.priceMember && (
<li className={styles.priceItem}> <li className={styles.priceItem}>
<span>Gratis</span> <span>Gratis</span>
</li> </li>
)} )}
{event.priceRegular && ( {typeof event.priceRegular === "number" && (
<li className={styles.priceItem}> <li className={styles.priceItem}>
<span className={styles.priceLabel}>Ordinær:</span>{" "} <span className={styles.priceLabel}>Ordinær:</span>{" "}
{event.priceRegular} kr {formatPrice(event.priceRegular)}
</li> </li>
)} )}
{event.priceStudent && ( {typeof event.priceStudent === "number" && (
<li className={styles.priceItem}> <li className={styles.priceItem}>
<span className={styles.priceLabel}>Student:</span>{" "} <span className={styles.priceLabel}>Student:</span>{" "}
{event.priceStudent} kr {formatPrice(event.priceStudent)}
</li> </li>
)} )}
{event.priceMember && ( {typeof event.priceMember === "number" && (
<li className={styles.priceItem}> <li className={styles.priceItem}>
<span className={styles.priceLabel}>Medlem:</span>{" "} <span className={styles.priceLabel}>Medlem:</span>{" "}
{event.priceMember} kr {formatPrice(event.priceMember)}
</li> </li>
)} )}
</ul> </ul>

View File

@ -33,7 +33,7 @@ const documents = {
"\n query home {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n home: page(contentType: \"home.HomePage\", urlPath: \"/home/\") {\n ... on HomePage {\n ...Home\n }\n }\n news: pages(contentType: \"news.newsPage\", limit: 3) {\n ... on NewsPage {\n ...News\n }\n }\n }\n ": types.HomeDocument, "\n query home {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n home: page(contentType: \"home.HomePage\", urlPath: \"/home/\") {\n ... on HomePage {\n ...Home\n }\n }\n news: pages(contentType: \"news.newsPage\", limit: 3) {\n ... on NewsPage {\n ...News\n }\n }\n }\n ": types.HomeDocument,
"\n fragment Blocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n ... on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n ... on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n ... on HorizontalRuleBlock {\n color\n }\n ... on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n }\n": types.BlocksFragmentDoc, "\n fragment Blocks on StreamFieldInterface {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n ... on ImageWithTextBlock {\n image {\n ...Image\n }\n imageFormat\n text\n }\n ... on ImageSliderBlock {\n images {\n ... on ImageSliderItemBlock {\n image {\n ...Image\n }\n text\n }\n }\n }\n ... on HorizontalRuleBlock {\n color\n }\n ... on FeaturedBlock {\n title\n featuredBlockText: text\n linkText\n imagePosition\n featuredPage {\n contentType\n pageType\n url\n ... on EventPage {\n featuredImage {\n ...Image\n }\n }\n ... on NewsPage {\n featuredImage {\n ...Image\n }\n }\n }\n featuredImageOverride {\n ...Image\n }\n }\n }\n": types.BlocksFragmentDoc,
"\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc, "\n fragment Image on CustomImage {\n id\n url\n width\n height\n alt\n attribution\n }\n": types.ImageFragmentDoc,
"\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n": types.EventFragmentDoc, "\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n": types.EventFragmentDoc,
"\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n }\n": types.FutureEventsDocument, "\n query futureEvents {\n events: eventIndex {\n ... on EventIndex {\n futureEvents {\n ... on EventPage {\n ...Event\n }\n }\n }\n }\n eventCategories: eventCategories {\n ... on EventCategory {\n name\n slug\n showInFilters\n }\n }\n }\n": types.FutureEventsDocument,
"\n fragment News on NewsPage {\n __typename\n id\n slug\n title\n firstPublishedAt\n excerpt\n featuredImage {\n ...Image\n }\n body {\n ...Blocks\n }\n }\n": types.NewsFragmentDoc, "\n fragment News on NewsPage {\n __typename\n id\n slug\n title\n firstPublishedAt\n excerpt\n featuredImage {\n ...Image\n }\n body {\n ...Blocks\n }\n }\n": types.NewsFragmentDoc,
"\n fragment NewsIndex on NewsIndex {\n __typename\n id\n slug\n title\n lead\n }\n": types.NewsIndexFragmentDoc, "\n fragment NewsIndex on NewsIndex {\n __typename\n id\n slug\n title\n lead\n }\n": types.NewsIndexFragmentDoc,
@ -137,7 +137,7 @@ export function graphql(source: "\n fragment Image on CustomImage {\n id\n
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n"): (typeof documents)["\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n"]; export function graphql(source: "\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n"): (typeof documents)["\n fragment Event on EventPage {\n __typename\n id\n slug\n title\n body {\n id\n blockType\n field\n ... on RichTextBlock {\n rawValue\n value\n }\n }\n featuredImage {\n ...Image\n }\n pig\n facebookUrl\n ticketUrl\n free\n priceRegular\n priceMember\n priceStudent\n categories {\n ... on EventCategory {\n name\n slug\n }\n }\n occurrences {\n ... on EventOccurrence {\n __typename\n id\n start\n end\n venue {\n __typename\n id\n slug\n title\n preposition\n url\n }\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

File diff suppressed because one or more lines are too long

View File

@ -43,6 +43,7 @@ const EventFragmentDefinition = graphql(`
pig pig
facebookUrl facebookUrl
ticketUrl ticketUrl
free
priceRegular priceRegular
priceMember priceMember
priceStudent priceStudent