added event pricing
This commit is contained in:
@@ -27,9 +27,11 @@ class Event extends Model
|
||||
'registration_type',
|
||||
'registration_data',
|
||||
'hero',
|
||||
'content'
|
||||
'content',
|
||||
'price'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the post's attachments.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->string('price')->default("");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->dropColumn('price');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user