This commit is contained in:
2023-02-27 22:29:34 +10:00
parent 955c06f5aa
commit eb0064e477
21 changed files with 422 additions and 239 deletions

View File

@@ -166,6 +166,7 @@ code {
flex-direction: row;
flex: 1;
margin-top: map-get($spacer, 5);
min-height: 50vh;
.image {
flex: 1;

View File

@@ -27,6 +27,12 @@ $success-color: #198754;
$success-color-dark: #12653e;
$success-color-darker: #0c4329;
$warning-color-lighter: #fff8e2;
$warning-color-light: #fff6d9;
$warning-color: #fff3cd;
$warning-color-dark: #ffd75a;
$warning-color-darker: #ffc203;
$border-color: #dddddd;
$secondary-background-color: #efefef;

View File

@@ -26,6 +26,9 @@
import { computed, ComputedRef } from "vue";
import { RouteRecordRaw, useRoute } from "vue-router";
import { routes } from "../router";
import { useApplicationStore } from "../store/ApplicationStore";
const applicationStore = useApplicationStore();
/**
* Return a list of routes from the current page back to the root
@@ -76,6 +79,20 @@ const computedRouteCrumbs: ComputedRef<RouteRecordRaw[]> = computed(() => {
};
let itemList = findMatch(routes);
if (itemList) {
if (applicationStore.dynamicTitle.length > 0) {
let meta = {};
if ("meta" in itemList[itemList.length - 1]) {
meta = itemList[itemList.length - 1]["meta"];
}
meta["title"] = applicationStore.dynamicTitle;
itemList[itemList.length - 1]["meta"] = meta;
}
}
return itemList || [];
});
</script>

View File

@@ -13,14 +13,17 @@
@click="handleClick">
<ion-icon
v-if="icon && dropdown == null && iconLocation == 'before'"
:icon="icon" />
:icon="icon"
class="sm-button-icon-before" />
<span>{{ label }}</span>
<ion-icon
v-if="icon && dropdown == null && iconLocation == 'after'"
:icon="icon" />
:icon="icon"
class="sm-button-icon-after" />
<ion-icon
v-if="dropdown != null"
name="caret-down-outline"
class="sm-button-icon-dropdown"
@click.stop="handleClickToggleDropdown" />
<ul
v-if="dropdown != null"
@@ -189,17 +192,17 @@ a.sm-button,
span {
flex: 1;
border-right: 1px solid $primary-color;
border-right: 1px solid $primary-color-lighter;
padding-top: calc(#{map-get($spacer, 1)} / 1.5);
padding-bottom: calc(#{map-get($spacer, 1)} / 1.5);
padding-left: map-get($spacer, 3);
padding-right: map-get($spacer, 3);
}
ion-icon {
.sm-button-icon-dropdown {
height: 1rem;
width: 1rem;
padding: 0 map-get($spacer, 1) 0 map-get($spacer, 1);
padding: 0 0.3rem 0 0.2rem;
}
&:hover {
@@ -316,19 +319,39 @@ a.sm-button,
margin: 0;
background-color: #f8f8f8;
border: 1px solid $border-color;
border-radius: 8px;
color: $primary-color;
box-shadow: 0 0 14px rgba(0, 0, 0, 0.25);
box-shadow: 0 0 14px rgba(0, 0, 0, 0.5);
}
li {
padding: 12px 16px;
padding: map-get($spacer, 1);
font-size: 100%;
cursor: pointer;
transition: background-color 0.1s ease-in-out;
&:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
&:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
}
li:hover {
background-color: $primary-color;
color: #f8f8f8;
}
.sm-button-icon-before {
margin-right: map-get($spacer, 1);
}
.sm-button-icon-after {
margin-left: map-get($spacer, 1);
}
}
</style>

View File

@@ -113,7 +113,8 @@ const handleClickIndicator = (index: number) => {
*/
const handleCarouselUpdate = () => {
if (slides.value != null) {
slideElements.value = slides.value.querySelectorAll(".carousel-slide");
slideElements.value =
slides.value.querySelectorAll(".sm-carousel-slide");
maxSlide.value = slideElements.value.length - 1;
}

View File

@@ -5,6 +5,7 @@
{
'sm-input-active': inputActive,
'sm-feedback-invalid': feedbackInvalid,
'sm-input-small': small,
},
computedClassType,
]">
@@ -107,6 +108,11 @@ const props = defineProps({
type: String,
default: "text",
},
small: {
type: Boolean,
default: false,
required: false,
},
feedbackInvalid: {
type: String,
default: "",
@@ -249,7 +255,7 @@ const handleFocus = (event: Event) => {
const handleBlur = async (event: Event) => {
if (objControl) {
objControl.validate();
await objControl.validate();
objControl.isValid();
}
@@ -276,10 +282,6 @@ const handleMediaSelect = async (event) => {
</script>
<style lang="scss">
.sm-column > .sm-input-group {
margin-bottom: 0;
}
.sm-input-group {
position: relative;
display: flex;
@@ -288,6 +290,26 @@ const handleMediaSelect = async (event) => {
flex: 1;
width: 100%;
&.sm-input-small {
font-size: 80%;
&.sm-input-active {
label {
transform: translate(6px, -3px) scale(0.7);
}
input {
padding: calc(#{map-get($spacer, 1)} * 1.5) map-get($spacer, 2)
calc(#{map-get($spacer, 1)} / 2) map-get($spacer, 2);
}
}
input,
label {
padding: map-get($spacer, 1) map-get($spacer, 2);
}
}
&.sm-input-active {
label {
transform: translate(8px, -3px) scale(0.7);

View File

@@ -32,7 +32,7 @@
</template>
<script setup lang="ts">
import { watch, ref, Ref } from "vue";
import { ref, Ref, watch } from "vue";
import { openDialog } from "vue3-promise-dialog";
import { api } from "../helpers/api";
import { Media, MediaResponse } from "../helpers/api.types";
@@ -125,10 +125,6 @@ handleLoad();
</script>
<style lang="scss">
.sm-column > .sm-input-group {
margin-bottom: 0;
}
.sm-input-group.sm-input-attachments {
display: block;

View File

@@ -92,10 +92,6 @@ const hasPermission = (): boolean => {
&.sm-no-breadcrumbs {
margin-bottom: 0;
.sm-page {
padding-bottom: calc(map-get($spacer, 5) * 2);
}
}
.sm-page {

View File

@@ -41,7 +41,7 @@ const computedPaginationInfo = computed(() => {
}
const start = (props.modelValue - 1) * props.perPage + 1;
const end = start + props.perPage - 1;
const end = Math.min(start + props.perPage - 1, props.total);
return `${start} - ${end} of ${props.total}`;
});

View File

@@ -37,6 +37,11 @@
:block="true"
:label="button" />
</div>
<div
v-if="banner"
:class="['sm-panel-banner', `sm-panel-banner-${bannerType}`]">
{{ banner }}
</div>
</div>
</router-link>
</template>
@@ -119,6 +124,16 @@ const props = defineProps({
default: "primary",
required: false,
},
banner: {
type: String,
default: "",
required: false,
},
bannerType: {
type: String,
default: "primary",
required: false,
},
});
let styleObject = reactive({});
@@ -204,6 +219,8 @@ watch(
color: $font-color !important;
margin-bottom: map-get($spacer, 5);
transition: box-shadow 0.2s ease-in-out;
position: relative;
overflow: hidden;
&:hover {
color: $font-color;
@@ -296,5 +313,42 @@ watch(
.sm-panel-button {
margin-top: map-get($spacer, 4);
}
.sm-panel-banner {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 65px;
right: -10px;
height: 20px;
width: 120px;
font-size: 70%;
text-transform: uppercase;
font-weight: 800;
color: #fff;
background-color: $primary-color;
transform-origin: 100%;
transform: rotateZ(45deg);
&.sm-panel-banner-success {
background-color: $success-color;
}
&.sm-panel-banner-danger {
background-color: $danger-color;
font-size: 60%;
}
&.sm-panel-banner-warning {
background-color: $warning-color-darker;
color: $font-color;
font-size: 60%;
}
&.sm-panel-banner-expired {
background-color: purple;
}
}
}
</style>

View File

@@ -18,6 +18,7 @@ export interface EventResponse {
export interface EventCollection {
events: Event[];
total: number;
}
export interface Media {

View File

@@ -132,7 +132,7 @@ interface FormControlValidation {
const defaultFormControlValidation: FormControlValidation = {
validator: {
validate: (): ValidationResult => {
validate: async (): Promise<ValidationResult> => {
return defaultValidationResult;
},
},
@@ -148,7 +148,7 @@ type FormControlIsValid = () => boolean;
export interface FormControlObject {
value: string;
validate: () => ValidationResult;
validate: () => Promise<ValidationResult>;
validation: FormControlValidation;
clearValidations: FormControlClearValidations;
setValidationResult: FormControlSetValidation;
@@ -179,11 +179,11 @@ export const FormControl = (
this.validation.result = defaultValidationResult;
},
setValidationResult: createValidationResult,
validate: function () {
validate: async function () {
if (this.validation.validator) {
this.validation.result = this.validation.validator.validate(
this.value
);
this.validation.result =
await this.validation.validator.validate(this.value);
return this.validation.result;
}

View File

@@ -1,8 +1,8 @@
import { SMDate } from "./datetime";
import { bytesReadable } from "../helpers/types";
import { SMDate } from "./datetime";
export interface ValidationObject {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
export interface ValidationResult {
@@ -42,7 +42,7 @@ interface ValidationMinOptions {
}
interface ValidationMinObject extends ValidationMinOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationMinOptions: ValidationMinOptions = {
@@ -81,8 +81,8 @@ export function Min(
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid:
this.type == "String"
? value.toString().length >= this.min
@@ -92,7 +92,7 @@ export function Min(
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -110,7 +110,7 @@ interface ValidationMaxOptions {
}
interface ValidationMaxObject extends ValidationMaxOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationMaxOptions: ValidationMaxOptions = {
@@ -149,8 +149,8 @@ export function Max(
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid:
this.type == "String"
? value.toString().length <= this.max
@@ -160,7 +160,7 @@ export function Max(
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -173,7 +173,7 @@ interface ValidationPasswordOptions {
}
interface ValidationPasswordObject extends ValidationPasswordOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationPasswordOptions: ValidationPasswordOptions = {
@@ -194,8 +194,8 @@ export function Password(
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid: /(?=.*[A-Za-z])(?=.*\d)(?=.*[.@$!%*#?&])[A-Za-z\d.@$!%*#?&]{1,}$/.test(
value
),
@@ -204,7 +204,7 @@ export function Password(
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -217,7 +217,7 @@ interface ValidationEmailOptions {
}
interface ValidationEmailObject extends ValidationEmailOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationEmailOptions: ValidationEmailOptions = {
@@ -235,8 +235,8 @@ export function Email(options?: ValidationEmailOptions): ValidationEmailObject {
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid:
value.length == 0 ||
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value),
@@ -245,7 +245,7 @@ export function Email(options?: ValidationEmailOptions): ValidationEmailObject {
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -258,7 +258,7 @@ interface ValidationPhoneOptions {
}
interface ValidationPhoneObject extends ValidationPhoneOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationPhoneOptions: ValidationPhoneOptions = {
@@ -276,8 +276,8 @@ export function Phone(options?: ValidationPhoneOptions): ValidationPhoneObject {
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid:
value.length == 0 ||
/^(\+|00)?[0-9][0-9 \-().]{7,32}$/.test(value),
@@ -286,7 +286,7 @@ export function Phone(options?: ValidationPhoneOptions): ValidationPhoneObject {
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -299,7 +299,7 @@ interface ValidationNumberOptions {
}
interface ValidationNumberObject extends ValidationNumberOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationNumberOptions: ValidationNumberOptions = {
@@ -319,15 +319,15 @@ export function Number(
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid: value.length == 0 || /^0?\d+$/.test(value),
invalidMessages: [
typeof this.invalidMessage === "string"
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -346,7 +346,7 @@ interface ValidationDateOptions {
}
interface ValidationDateObject extends ValidationDateOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationDateOptions: ValidationDateOptions = {
@@ -372,7 +372,7 @@ export function Date(options?: ValidationDateOptions): ValidationDateObject {
return {
...options,
validate: function (value: string): ValidationResult {
validate: function (value: string): Promise<ValidationResult> {
let valid = true;
let invalidMessageType = "invalidMessage";
@@ -409,14 +409,14 @@ export function Date(options?: ValidationDateOptions): ValidationDateObject {
valid = false;
}
return {
return Promise.resolve({
valid: valid,
invalidMessages: [
typeof this[invalidMessageType] === "string"
? this[invalidMessageType]
: this[invalidMessageType](this),
],
};
});
},
};
}
@@ -435,7 +435,7 @@ interface ValidationTimeOptions {
}
interface ValidationTimeObject extends ValidationTimeOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationTimeOptions: ValidationTimeOptions = {
@@ -461,7 +461,7 @@ export function Time(options?: ValidationTimeOptions): ValidationTimeObject {
return {
...options,
validate: function (value: string): ValidationResult {
validate: function (value: string): Promise<ValidationResult> {
let valid = true;
let invalidMessageType = "invalidMessage";
@@ -498,14 +498,14 @@ export function Time(options?: ValidationTimeOptions): ValidationTimeObject {
valid = false;
}
return {
return Promise.resolve({
valid: valid,
invalidMessages: [
typeof this[invalidMessageType] === "string"
? this[invalidMessageType]
: this[invalidMessageType](this),
],
};
});
},
};
}
@@ -526,7 +526,7 @@ interface ValidationDateTimeOptions {
}
interface ValidationDateTimeObject extends ValidationDateTimeOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationDateTimeOptions: ValidationDateTimeOptions = {
@@ -554,7 +554,7 @@ export function DateTime(
return {
...options,
validate: function (value: string): ValidationResult {
validate: function (value: string): Promise<ValidationResult> {
let valid = true;
let invalidMessageType = "invalidMessage";
@@ -591,14 +591,14 @@ export function DateTime(
valid = false;
}
return {
return Promise.resolve({
valid: valid,
invalidMessages: [
typeof this[invalidMessageType] === "string"
? this[invalidMessageType]
: this[invalidMessageType](this),
],
};
});
},
};
}
@@ -606,7 +606,7 @@ export function DateTime(
/**
* CUSTOM
*/
type ValidationCustomCallback = (value: string) => boolean | string;
type ValidationCustomCallback = (value: string) => Promise<boolean | string>;
interface ValidationCustomOptions {
callback: ValidationCustomCallback;
@@ -618,7 +618,7 @@ interface ValidationCustomObject extends ValidationCustomOptions {
}
const defaultValidationCustomOptions: ValidationCustomOptions = {
callback: () => {
callback: async () => {
return true;
},
invalidMessage: "This field is invalid.",
@@ -692,24 +692,24 @@ export function Custom(
export const And = (list: Array<ValidationObject>) => {
return {
list: list,
validate: function (value: string) {
validate: async function (value: string) {
const validationResult: ValidationResult = {
valid: true,
invalidMessages: [],
};
this.list.every((item: ValidationObject) => {
const validationItemResult = item.validate(value);
if (validationItemResult.valid == false) {
validationResult.valid = false;
validationResult.invalidMessages =
validationResult.invalidMessages.concat(
validationItemResult.invalidMessages
);
}
return true;
});
await Promise.all(
this.list.map(async (item: ValidationObject) => {
const validationItemResult = await item.validate(value);
if (validationItemResult.valid == false) {
validationResult.valid = false;
validationResult.invalidMessages =
validationResult.invalidMessages.concat(
validationItemResult.invalidMessages
);
}
})
);
return validationResult;
},
@@ -724,7 +724,7 @@ interface ValidationRequiredOptions {
}
interface ValidationRequiredObject extends ValidationRequiredOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationRequiredOptions: ValidationRequiredOptions = {
@@ -744,15 +744,15 @@ export function Required(
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid: value.length > 0,
invalidMessages: [
typeof this.invalidMessage === "string"
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -765,7 +765,7 @@ interface ValidationUrlOptions {
}
interface ValidationUrlObject extends ValidationUrlOptions {
validate: (value: string) => ValidationResult;
validate: (value: string) => Promise<ValidationResult>;
}
const defaultValidationUrlOptions: ValidationUrlOptions = {
@@ -783,8 +783,8 @@ export function Url(options?: ValidationUrlOptions): ValidationUrlObject {
return {
...options,
validate: function (value: string): ValidationResult {
return {
validate: function (value: string): Promise<ValidationResult> {
return Promise.resolve({
valid: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*(:\d+)?([/?#][^\s]*)?$/.test(
value
),
@@ -793,7 +793,7 @@ export function Url(options?: ValidationUrlOptions): ValidationUrlObject {
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}
@@ -807,7 +807,7 @@ interface ValidationFileSizeOptions {
}
interface ValidationFileSizeObject extends ValidationFileSizeOptions {
validate: (value: File) => ValidationResult;
validate: (value: File) => Promise<ValidationResult>;
}
const defaultValidationFileSizeOptions: ValidationFileSizeOptions = {
@@ -830,15 +830,15 @@ export function FileSize(
return {
...options,
validate: function (value: File): ValidationResult {
return {
validate: function (value: File): Promise<ValidationResult> {
return Promise.resolve({
valid: value.size < options.size,
invalidMessages: [
typeof this.invalidMessage === "string"
? this.invalidMessage
: this.invalidMessage(this),
],
};
});
},
};
}

View File

@@ -29,29 +29,36 @@
:not-found="events.length == 0"
not-found-text="No workshops found">
<SMPanel
v-for="event in events"
:key="event.id"
:to="{ name: 'event-view', params: { id: event.id } }"
:title="event.title"
:image="event.hero"
v-for="item in events"
:key="item.event.id"
:to="{ name: 'event-view', params: { id: item.event.id } }"
:title="item.event.title"
:image="item.event.hero"
:show-time="true"
:date="event.start_at"
:end-date="event.end_at"
:date="item.event.start_at"
:end-date="item.event.end_at"
:date-in-image="true"
:location="
event.location == 'online'
item.event.location == 'online'
? 'Online Event'
: event.address
"></SMPanel>
: item.event.address
"
:banner="item.banner"
:banner-type="item.bannerType"></SMPanel>
</SMPanelList>
<SMPagination
v-model="postsPage"
:total="postsTotal"
:per-page="postsPerPage" />
</template>
</SMPage>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { reactive, ref, watch } from "vue";
import SMInput from "../components/SMInput.vue";
import SMMessage from "../components/SMMessage.vue";
import SMPagination from "../components/SMPagination.vue";
import SMPanel from "../components/SMPanel.vue";
import SMPanelList from "../components/SMPanelList.vue";
import SMToolbar from "../components/SMToolbar.vue";
@@ -59,8 +66,14 @@ import { api } from "../helpers/api";
import { Event, EventCollection } from "../helpers/api.types";
import { SMDate } from "../helpers/datetime";
interface EventData {
event: Event;
banner: string;
bannerType: string;
}
const loading = ref(true);
let events: Event[] = reactive([]);
let events: EventData[] = reactive([]);
const dateRangeError = ref("");
const formMessage = ref("");
@@ -69,12 +82,15 @@ const filterKeywords = ref("");
const filterLocation = ref("");
const filterDateRange = ref("");
const postsPerPage = 9;
let postsPage = ref(1);
let postsTotal = ref(0);
/**
* Load page data.
*/
const handleLoad = async () => {
let query = {};
query["limit"] = 10;
if (filterKeywords.value && filterKeywords.value.length > 0) {
query["q"] = filterKeywords.value;
@@ -116,12 +132,20 @@ const handleLoad = async () => {
formMessage.value = "";
events = [];
if (Object.keys(query).length == 1 && Object.keys(query)[0] == "limit") {
if (Object.keys(query).length == 0) {
const now = new Date();
const startingDate = new Date(now.setDate(now.getDate() - 14));
query["end_at"] =
">" +
new SMDate("now").format("yyyy/MM/dd HH:mm:ss", { utc: true });
new SMDate(startingDate).format("yyyy/MM/dd HH:mm:ss", {
utc: true,
});
}
query["limit"] = postsPerPage;
query["page"] = postsPage.value;
api.get({
url: "/events",
params: query,
@@ -129,19 +153,50 @@ const handleLoad = async () => {
.then((result) => {
const data = result.data as EventCollection;
postsTotal.value = data.total;
if (data && data.events) {
events = data.events;
events = [];
events.forEach((item) => {
item.start_at = new SMDate(item.start_at, {
data.events.forEach((item) => {
let banner = "";
let bannerType = "";
const parsedStartAt = new SMDate(item.start_at, {
format: "yyyy-MM-dd HH:mm:ss",
utc: true,
}).format("yyyy-MM-dd HH:mm:ss");
});
item.end_at = new SMDate(item.end_at, {
const parsedEndAt = new SMDate(item.end_at, {
format: "yyyy-MM-dd HH:mm:ss",
utc: true,
}).format("yyyy-MM-dd HH:mm:ss");
});
item.start_at = parsedStartAt.format("yyyy-MM-dd HH:mm:ss");
item.end_at = parsedEndAt.format("yyyy-MM-dd HH:mm:ss");
if (
parsedEndAt.isBefore(new SMDate("now")) ||
item.status == "closed"
) {
banner = "closed";
bannerType = "expired";
} else if (item.status == "open") {
banner = "open";
bannerType = "success";
} else if (item.status == "cancelled") {
banner = "cancelled";
bannerType = "danger";
} else if (item.status == "soon") {
banner = "Open Soon";
bannerType = "warning";
}
events.push({
event: item,
banner: banner,
bannerType: bannerType,
});
});
}
})
@@ -161,6 +216,13 @@ const handleFilter = async () => {
handleLoad();
};
watch(
() => postsPage.value,
() => {
handleLoad();
}
);
handleLoad();
</script>

View File

@@ -187,37 +187,37 @@ const registerUrl = computed(() => {
const handleLoad = async () => {
formMessage.value = "";
api.get({
url: "/events/{event}",
params: {
event: route.params.id,
},
})
.then((result) => {
const eventData = result.data as EventResponse;
if (eventData && eventData.event) {
event.value = eventData.event;
event.value.start_at = new SMDate(event.value.start_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
event.value.end_at = new SMDate(event.value.end_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
applicationStore.setDynamicTitle(event.value.title);
handleLoadImage();
} else {
pageError = 404;
}
})
.catch((error) => {
formMessage.value =
error.data?.message ||
"Could not load event information from the server.";
try {
let result = await api.get({
url: "/events/{event}",
params: {
event: route.params.id,
},
});
const eventData = result.data as EventResponse;
if (eventData && eventData.event) {
event.value = eventData.event;
event.value.start_at = new SMDate(event.value.start_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
event.value.end_at = new SMDate(event.value.end_at, {
format: "ymd",
utc: true,
}).format("yyyy/MM/dd HH:mm:ss");
applicationStore.setDynamicTitle(event.value.title);
handleLoadImage();
} else {
pageError = 404;
}
} catch (error) {
formMessage.value =
error.data?.message ||
"Could not load event information from the server.";
}
};
/**

View File

@@ -152,65 +152,66 @@ const handleLoad = async () => {
let posts = [];
let events = [];
api.get({
url: "/posts",
params: {
limit: 3,
},
})
.then((result) => {
const data = result.data as PostCollection;
if (data && data.posts) {
data.posts.forEach((post) => {
posts.push({
title: post.title,
content: excerpt(post.content, 200),
image: post.hero,
url: { name: "post-view", params: { slug: post.slug } },
cta: "Read More...",
});
});
}
})
.catch(() => {
/* empty */
try {
const result = await api.get({
url: "/posts",
params: {
limit: 3,
},
});
api.get({
url: "/events",
params: {
limit: 3,
end_at:
">" +
new SMDate("now").format("yyyy-MM-dd HH:mm:ss", {
utc: true,
}),
},
})
.then((result) => {
const data = result.data as EventCollection;
const data = result.data as PostCollection;
if (data && data.events) {
data.events.forEach((event) => {
events.push({
title: event.title,
content: excerpt(event.content, 200),
image: event.hero,
url: { name: "event-view", params: { id: event.id } },
cta: "View Workshop",
});
if (data && data.posts) {
data.posts.forEach((post) => {
posts.push({
title: post.title,
content: excerpt(post.content, 200),
image: post.hero,
url: { name: "post-view", params: { slug: post.slug } },
cta: "Read More...",
});
}
})
.catch(() => {
/* empty */
});
}
} catch {
/* empty */
}
try {
const result = await api.get({
url: "/events",
params: {
limit: 3,
end_at:
">" +
new SMDate("now").format("yyyy-MM-dd HH:mm:ss", {
utc: true,
}),
},
});
const data = result.data as EventCollection;
if (data && data.events) {
data.events.forEach((event) => {
events.push({
title: event.title,
content: excerpt(event.content, 200),
image: event.hero,
url: { name: "event-view", params: { id: event.id } },
cta: "View Workshop",
});
});
}
} catch {
/* empty */
}
for (let i = 1; i <= Math.max(posts.length, events.length); i++) {
if (i <= posts.length) {
slides.value.push(posts[i - 1]);
}
if (i <= events.length) {
slides.value.push(events[i - 1]);
}

View File

@@ -35,7 +35,7 @@
</SMRow>
<SMFormFooter>
<template #left>
<div>
<div class="small">
<span class="pr-1"
>Already have an account?</span
><router-link to="/login"
@@ -92,42 +92,33 @@ import {
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
let abortController: AbortController | null = null;
const checkUsername = (value: string): boolean | string => {
if (lastUsernameCheck.value != value) {
lastUsernameCheck.value = value;
const checkUsername = async (value: string): Promise<boolean | string> => {
try {
if (lastUsernameCheck.value != value) {
lastUsernameCheck.value = value;
if (abortController != null) {
abortController.abort();
abortController = null;
if (abortController != null) {
abortController.abort();
abortController = null;
}
abortController = new AbortController();
await api.get({
url: "/users",
params: {
username: `=${value}`,
},
signal: abortController.signal,
});
return "The username has already been taken.";
}
abortController = new AbortController();
api.get({
url: "/users",
params: {
username: value,
},
signal: abortController.signal,
})
.then((response) => {
console.log("The username has already been taken.", response);
return "The username has already been taken.";
})
.catch((error) => {
console.log(error);
if (error.status != 404) {
return (
error.json?.message ||
"An unexpected server error occurred."
);
}
return true;
});
return true;
} catch (error) {
return true;
}
return true;
};
const formDone = ref(false);

View File

@@ -30,7 +30,10 @@
</template>
<template #item-title="item">
<router-link
:to="{ name: 'event-edit', params: { id: item.id } }"
:to="{
name: 'dashboard-event-edit',
params: { id: item.id },
}"
>{{ item.title }}</router-link
>
</template>
@@ -164,15 +167,15 @@ watch(search, () => {
});
const handleClickRow = (item) => {
router.push({ name: "event-edit", params: { id: item.id } });
router.push({ name: "dashboard-event-edit", params: { id: item.id } });
};
const handleCreate = () => {
router.push({ name: "event-create" });
router.push({ name: "dashboard-event-create" });
};
const handleEdit = (item) => {
router.push({ name: "event-edit", params: { id: item.id } });
router.push({ name: "dashboard-event-edit", params: { id: item.id } });
};
const handleDelete = async (item) => {

View File

@@ -178,11 +178,11 @@ watch(search, (value) => {
});
const handleClickRow = (item) => {
router.push({ name: "media-edit", params: { id: item.id } });
router.push({ name: "dashboard-media-edit", params: { id: item.id } });
};
const handleEdit = (item) => {
router.push({ name: "media-edit", params: { id: item.id } });
router.push({ name: "dashboard-media-edit", params: { id: item.id } });
};
const handleDelete = async (item) => {

View File

@@ -12,10 +12,15 @@
<SMButton
type="primary"
label="Create Post"
:small="true"
@click="handleCreate" />
</template>
<template #right>
<input v-model="search" placeholder="Search" />
<SMInput
v-model="search"
label="Search"
:small="true"
style="max-width: 250px" />
</template>
</SMToolbar>
@@ -31,7 +36,10 @@
</template>
<template #item-title="item">
<router-link
:to="{ name: 'post-edit', params: { id: item.id } }"
:to="{
name: 'dashboard-post-edit',
params: { id: item.id },
}"
>{{ item.title }}</router-link
>
</template>
@@ -58,6 +66,7 @@ import { openDialog } from "vue3-promise-dialog";
import SMDialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import SMButton from "../../components/SMButton.vue";
import SMHeading from "../../components/SMHeading.vue";
import SMInput from "../../components/SMInput.vue";
import SMLoadingIcon from "../../components/SMLoadingIcon.vue";
import SMMessage from "../../components/SMMessage.vue";
import SMToolbar from "../../components/SMToolbar.vue";
@@ -181,15 +190,15 @@ watch(search, () => {
});
const handleClickRow = (item) => {
router.push({ name: "post-edit", params: { id: item.id } });
router.push({ name: "dashboard-post-edit", params: { id: item.id } });
};
const handleCreate = () => {
router.push({ name: "post-create" });
router.push({ name: "dashboard-post-create" });
};
const handleEdit = (item) => {
router.push({ name: "post-edit", params: { id: item.id } });
router.push({ name: "dashboard-post-edit", params: { id: item.id } });
};
const handleDelete = async (item) => {

View File

@@ -134,7 +134,7 @@ const bodyItemClassNameFunction = (column) => {
};
const handleEdit = (user) => {
router.push({ name: "user-edit", params: { id: user.id } });
router.push({ name: "dashboard-user-edit", params: { id: user.id } });
};
const handleDelete = async (user) => {