added event pricing

This commit is contained in:
2023-02-28 19:16:10 +10:00
parent 041dd4b314
commit 9fd46b9fd9
7 changed files with 184 additions and 38 deletions

View File

@@ -19,14 +19,19 @@
<div v-if="showDate && date" class="sm-panel-date">
<ion-icon
v-if="showTime == false && endDate.length == 0"
name="calendar-outline" />
<ion-icon v-else name="time-outline" />
name="calendar-outline"
class="icon" />
<ion-icon v-else name="time-outline" class="icon" />
<p>{{ computedDate }}</p>
</div>
<div v-if="location" class="sm-panel-location">
<ion-icon name="location-outline" />
<ion-icon class="icon" name="location-outline" />
<p>{{ location }}</p>
</div>
<div v-if="price" class="sm-panel-price">
<span class="icon">$</span>
<p>{{ computedPrice }}</p>
</div>
<div v-if="content" class="sm-panel-content">
{{ computedContent }}
</div>
@@ -52,7 +57,12 @@ import { api } from "../helpers/api";
import { MediaResponse } from "../helpers/api.types";
import { SMDate } from "../helpers/datetime";
import { imageLoad } from "../helpers/image";
import { excerpt, replaceHtmlEntites, stripHtmlTags } from "../helpers/string";
import {
excerpt,
replaceHtmlEntites,
stringToNumber,
stripHtmlTags,
} from "../helpers/string";
import { isUUID } from "../helpers/uuid";
import SMButton from "./SMButton.vue";
@@ -134,6 +144,11 @@ const props = defineProps({
default: "primary",
required: false,
},
price: {
type: String,
default: "",
required: false,
},
});
let styleObject = reactive({});
@@ -180,14 +195,32 @@ const computedContent = computed(() => {
return excerpt(replaceHtmlEntites(stripHtmlTags(props.content)), 200);
});
/**
* Return a computed day number from props.date
*/
const computedDay = computed(() => {
return new SMDate(props.date, { format: "yMd" }).format("dd");
});
/**
* Return a computed month name from props.date
*/
const computedMonth = computed(() => {
return new SMDate(props.date, { format: "yMd" }).format("MMM");
});
/**
* Return a computed price amount, if a form of 0, return "Free"
*/
const computedPrice = computed(() => {
const parsedPrice = stringToNumber(props.price);
if (parsedPrice == 0) {
return "Free";
}
return props.price;
});
onMounted(async () => {
if (props.image && props.image.length > 0 && isUUID(props.image)) {
api.get({ url: "/media/{medium}", params: { medium: props.image } })
@@ -290,19 +323,21 @@ watch(
}
.sm-panel-date,
.sm-panel-location {
.sm-panel-location,
.sm-panel-price {
display: flex;
flex-direction: row;
align-items: top;
font-size: 80%;
margin-bottom: 0.4rem;
ion-icon {
.icon {
flex: 0 1 1rem;
margin-right: map-get($spacer, 1);
padding-top: 0.1rem;
height: 1rem;
padding: 0.25rem 0;
text-align: center;
}
p {

View File

@@ -5,11 +5,13 @@ export interface Event {
content: string;
start_at: string;
end_at: string;
publish_at: string;
location: string;
address: string;
status: string;
registration_type: string;
registration_data: string;
price: string;
}
export interface EventResponse {

View File

@@ -79,3 +79,39 @@ export const replaceHtmlEntites = (txt: string): string => {
return translate[entity];
});
};
/**
* Convert a string to a number, ignoring items like dollar signs, etc.
*
* @param {string} str The string to convert to a number
* @returns {number} A number with the minimum amount of decimal places (or 0)
*/
export const stringToNumber = (str: string): number => {
str = str.replace(/[^\d.-]/g, "");
const num = Number.parseFloat(str);
return isNaN(num) ? 0 : parseFloat(num.toFixed(2));
};
/**
* Convert a number or string to a price (0 or 0.00).
*
* @param {number|string} numOrString The number of string to convert to a price.
* @returns {string} The converted result.
*/
export const toPrice = (numOrString: number | string): string => {
let num = 0;
if (typeof numOrString == "string") {
num = stringToNumber(numOrString);
} else {
num = numOrString;
}
if (num % 1 === 0) {
// Number has no decimal places
return num.toFixed(0);
} else {
// Number has decimal places
return num.toFixed(2);
}
};

View File

@@ -61,7 +61,11 @@
label="Register for Event"></SMButton>
</div>
<div class="sm-workshop-date">
<h4><ion-icon name="calendar-outline" />Date / Time</h4>
<h4>
<ion-icon
class="icon"
name="calendar-outline" />Date / Time
</h4>
<p
v-for="(line, index) in workshopDate"
:key="index"
@@ -70,7 +74,11 @@
</p>
</div>
<div class="sm-workshop-location">
<h4><ion-icon name="location-outline" />Location</h4>
<h4>
<ion-icon
class="icon"
name="location-outline" />Location
</h4>
<p>
{{
event.location == "online"
@@ -79,6 +87,9 @@
}}
</p>
</div>
<div v-if="event.price" class="sm-workshop-price">
<h4><span class="icon">$</span>{{ computedPrice }}</h4>
</div>
</div>
</SMContainer>
</SMContainer>
@@ -95,6 +106,7 @@ import { api } from "../helpers/api";
import { Event, EventResponse, MediaResponse } from "../helpers/api.types";
import { SMDate } from "../helpers/datetime";
import { imageLoad } from "../helpers/image";
import { stringToNumber } from "../helpers/string";
import { useApplicationStore } from "../store/ApplicationStore";
const applicationStore = useApplicationStore();
@@ -162,6 +174,18 @@ const workshopDate = computed(() => {
return str;
});
/**
* Return a computed price amount, if a form of 0, return "Free"
*/
const computedPrice = computed(() => {
const parsedPrice = stringToNumber(event.value.price || "0");
if (parsedPrice == 0) {
return "Free";
}
return event.value.price;
});
const registerUrl = computed(() => {
let href = "";
@@ -290,10 +314,11 @@ handleLoad();
align-items: center;
height: 1rem;
ion-icon {
.icon {
display: inline-block;
width: 1rem;
margin-right: 0.5rem;
text-align: center;
}
}
@@ -329,7 +354,8 @@ handleLoad();
}
.sm-workshop-date,
.sm-workshop-location {
.sm-workshop-location,
.sm-workshop-price {
padding: 0 1rem;
}
}

View File

@@ -60,6 +60,11 @@
</SMColumn>
</SMRow>
<SMRow>
<SMColumn>
<SMInput control="price"
>Leave blank to hide from public.</SMInput
>
</SMColumn>
<SMColumn>
<SMInput
type="select"
@@ -71,6 +76,8 @@
link: 'Link',
}" />
</SMColumn>
</SMRow>
<SMRow>
<SMColumn>
<SMInput
v-if="registration_data?.visible"
@@ -136,6 +143,7 @@ import {
} from "../../helpers/validate";
import SMInputAttachments from "../../components/SMInputAttachments.vue";
import SMForm from "../../components/SMForm.vue";
import { EventResponse } from "../../helpers/api.types";
const route = useRoute();
const page_title = route.params.id ? "Edit Event" : "Create New Event";
@@ -230,54 +238,58 @@ const form = reactive(
),
hero: FormControl("", Required()),
content: FormControl(),
price: FormControl(),
})
);
const loadData = async () => {
form.loading(true);
if (route.params.id) {
try {
let res = await api.get("/events/" + route.params.id);
if (!res.data.event) {
form.loading(true);
const result = await api.get({
url: "/events/{id}",
params: { id: route.params.id },
});
const data = result.data as EventResponse;
if (!data || !data.event) {
throw new Error("The server is currently not available");
}
form.controls.title.value = res.data.event.title;
form.controls.location.value = res.data.event.location;
form.controls.address.value = res.data.event.address
? res.data.event.address
form.controls.title.value = data.event.title;
form.controls.location.value = data.event.location;
form.controls.address.value = data.event.address
? data.event.address
: "";
form.controls.start_at.value = new SMDate(res.data.event.start_at, {
form.controls.start_at.value = new SMDate(data.event.start_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
form.controls.end_at.value = new SMDate(res.data.event.end_at, {
}).format("yyyy/MM/dd HH:mm");
form.controls.end_at.value = new SMDate(data.event.end_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
form.controls.status.value = res.data.event.status;
form.controls.publish_at.value = new SMDate(
res.data.event.publish_at,
{
format: "ymd",
utc: true,
}
).format("yyyy/MM/dd HH:mm:ss");
}).format("yyyy/MM/dd HH:mm");
form.controls.status.value = data.event.status;
form.controls.publish_at.value = new SMDate(data.event.publish_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm");
form.controls.registration_type.value =
res.data.event.registration_type;
data.event.registration_type;
form.controls.registration_data.value =
res.data.event.registration_data;
form.controls.content.value = res.data.event.content
? res.data.event.content
data.event.registration_data;
form.controls.content.value = data.event.content
? data.event.content
: "";
form.controls.hero.value = res.data.event.hero;
form.controls.hero.value = data.event.hero;
form.controls.price.value = data.event.price;
} catch (err) {
pageError.value = err.response.status;
} finally {
form.loading(false);
}
}
form.loading(false);
};
const handleSubmit = async () => {
@@ -303,6 +315,7 @@ const handleSubmit = async () => {
registration_data: form.controls.registration_data.value,
content: form.controls.content.value,
hero: form.controls.hero.value,
price: form.controls.price.value,
};
if (route.params.id) {