cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
interface ImportMeta {
|
export interface ImportMetaExtras extends ImportMeta {
|
||||||
env: {
|
env: {
|
||||||
APP_URL: string;
|
APP_URL: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'sm-button',
|
'sm-button',
|
||||||
classType,
|
classType,
|
||||||
|
{ 'sm-button-small': small },
|
||||||
{ 'sm-button-block': block },
|
{ 'sm-button-block': block },
|
||||||
{ 'sm-dropdown-button': dropdown },
|
{ 'sm-dropdown-button': dropdown },
|
||||||
]"
|
]"
|
||||||
@@ -37,7 +38,12 @@
|
|||||||
v-else-if="!isEmpty(to) && typeof to == 'string'"
|
v-else-if="!isEmpty(to) && typeof to == 'string'"
|
||||||
:href="to"
|
:href="to"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="['sm-button', classType, { 'sm-button-block': block }]"
|
:class="[
|
||||||
|
'sm-button',
|
||||||
|
classType,
|
||||||
|
{ 'sm-button-small': small },
|
||||||
|
{ 'sm-button-block': block },
|
||||||
|
]"
|
||||||
:type="buttonType">
|
:type="buttonType">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<ion-icon v-if="icon" :icon="icon" />
|
<ion-icon v-if="icon" :icon="icon" />
|
||||||
@@ -46,7 +52,12 @@
|
|||||||
v-else-if="!isEmpty(to) && typeof to == 'object'"
|
v-else-if="!isEmpty(to) && typeof to == 'object'"
|
||||||
:to="to"
|
:to="to"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="['sm-button', classType, { 'sm-button-block': block }]">
|
:class="[
|
||||||
|
'sm-button',
|
||||||
|
classType,
|
||||||
|
{ 'sm-button-small': small },
|
||||||
|
{ 'sm-button-block': block },
|
||||||
|
]">
|
||||||
<ion-icon v-if="icon && iconLocation == 'before'" :icon="icon" />
|
<ion-icon v-if="icon && iconLocation == 'before'" :icon="icon" />
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<ion-icon v-if="icon && iconLocation == 'after'" :icon="icon" />
|
<ion-icon v-if="icon && iconLocation == 'after'" :icon="icon" />
|
||||||
@@ -67,7 +78,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
iconLocation: {
|
iconLocation: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "before",
|
default: "after",
|
||||||
required: false,
|
required: false,
|
||||||
validator: (value: string) => {
|
validator: (value: string) => {
|
||||||
return ["before", "after"].includes(value);
|
return ["before", "after"].includes(value);
|
||||||
@@ -89,6 +100,11 @@ const props = defineProps({
|
|||||||
default: false,
|
default: false,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
small: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ const emits = defineEmits(["submit"]);
|
|||||||
/**
|
/**
|
||||||
* Handle the user submitting the form.
|
* Handle the user submitting the form.
|
||||||
*/
|
*/
|
||||||
const handleSubmit = function () {
|
const handleSubmit = async function () {
|
||||||
if (props.modelValue.validate()) {
|
if (await props.modelValue.validate()) {
|
||||||
emits("submit");
|
emits("submit");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import "../../../import-meta";
|
import { ImportMetaExtras } from "../../../import-meta";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
html: {
|
html: {
|
||||||
@@ -22,7 +22,9 @@ const computedContent = computed(() => {
|
|||||||
let html = "";
|
let html = "";
|
||||||
|
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
`<a ([^>]*?)href="${import.meta.env.APP_URL}(.*?>.*?)</a>`,
|
`<a ([^>]*?)href="${
|
||||||
|
(import.meta as ImportMetaExtras).env.APP_URL
|
||||||
|
}(.*?>.*?)</a>`,
|
||||||
"ig"
|
"ig"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,7 @@ const handleMediaSelect = async (event) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: map-get($spacer, 4);
|
margin-bottom: map-get($spacer, 4);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&.sm-input-active {
|
&.sm-input-active {
|
||||||
label {
|
label {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<SMButton
|
<SMButton
|
||||||
:to="{ name: 'workshop-list' }"
|
:to="{ name: 'event-list' }"
|
||||||
class="sm-navbar-cta"
|
class="sm-navbar-cta"
|
||||||
label="Find a workshop"
|
label="Find a workshop"
|
||||||
icon="arrow-forward-outline" />
|
icon="arrow-forward-outline" />
|
||||||
@@ -70,13 +70,13 @@ const menuItems = [
|
|||||||
{
|
{
|
||||||
name: "news",
|
name: "news",
|
||||||
label: "News",
|
label: "News",
|
||||||
to: { name: "news" },
|
to: { name: "post-list" },
|
||||||
icon: "newspaper-outline",
|
icon: "newspaper-outline",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "workshops",
|
name: "workshops",
|
||||||
label: "Workshops",
|
label: "Workshops",
|
||||||
to: { name: "workshop-list" },
|
to: { name: "event-list" },
|
||||||
icon: "library-outline",
|
icon: "library-outline",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
135
resources/js/components/SMPagination.vue
Normal file
135
resources/js/components/SMPagination.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sm-pagination">
|
||||||
|
<ion-icon
|
||||||
|
name="chevron-back-outline"
|
||||||
|
:class="[{ disabled: computedDisablePrevButton }]"
|
||||||
|
@click="handleClickPrev" />
|
||||||
|
<span class="sm-pagination-info">{{ computedPaginationInfo }}</span>
|
||||||
|
<ion-icon
|
||||||
|
name="chevron-forward-outline"
|
||||||
|
:class="[{ disabled: computedDisableNextButton }]"
|
||||||
|
@click="handleClickNext" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
perPage: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pagination info
|
||||||
|
*/
|
||||||
|
const computedPaginationInfo = computed(() => {
|
||||||
|
if (props.total == 0) {
|
||||||
|
return "0 - 0 of 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (props.modelValue - 1) * props.perPage + 1;
|
||||||
|
const end = start + props.perPage - 1;
|
||||||
|
|
||||||
|
return `${start} - ${end} of ${props.total}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the total number of pages.
|
||||||
|
*/
|
||||||
|
const computedTotalPages = computed(() => {
|
||||||
|
return Math.ceil(props.total / props.perPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the previous button should be disabled.
|
||||||
|
*/
|
||||||
|
const computedDisablePrevButton = computed(() => {
|
||||||
|
return props.modelValue <= 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the next button should be disabled.
|
||||||
|
*/
|
||||||
|
const computedDisableNextButton = computed(() => {
|
||||||
|
return props.modelValue >= computedTotalPages.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle click on previous button
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} $event The mouse event.
|
||||||
|
*/
|
||||||
|
const handleClickPrev = ($event: MouseEvent): void => {
|
||||||
|
if (
|
||||||
|
$event.target &&
|
||||||
|
($event.target as HTMLElement).classList.contains("disabled") ==
|
||||||
|
false &&
|
||||||
|
props.modelValue > 1
|
||||||
|
) {
|
||||||
|
emits("update:modelValue", props.modelValue - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle click on next button
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} $event The mouse event.
|
||||||
|
*/
|
||||||
|
const handleClickNext = ($event: MouseEvent): void => {
|
||||||
|
if (
|
||||||
|
$event.target &&
|
||||||
|
($event.target as HTMLElement).classList.contains("disabled") ==
|
||||||
|
false &&
|
||||||
|
props.modelValue < computedTotalPages.value
|
||||||
|
) {
|
||||||
|
emits("update:modelValue", props.modelValue + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sm-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
border: 1px solid $secondary-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.1s ease-in-out, background-color 0.1s ease-in-out;
|
||||||
|
color: $font-color;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: $secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.disabled) {
|
||||||
|
&:hover {
|
||||||
|
background-color: $secondary-color;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-pagination-info {
|
||||||
|
margin: 0 map-get($spacer, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -31,7 +31,11 @@
|
|||||||
{{ computedContent }}
|
{{ computedContent }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="button.length > 0" class="sm-panel-button">
|
<div v-if="button.length > 0" class="sm-panel-button">
|
||||||
<SMButton :to="to" :type="buttonType" :label="button" />
|
<SMButton
|
||||||
|
:to="to"
|
||||||
|
:type="buttonType"
|
||||||
|
:block="true"
|
||||||
|
:label="button" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -288,5 +292,9 @@ watch(
|
|||||||
line-height: 130%;
|
line-height: 130%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm-panel-button {
|
||||||
|
margin-top: map-get($spacer, 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sm-toolbar">
|
<div class="sm-toolbar">
|
||||||
<div class="sm-toolbar-column sm-toolbar-column-left">
|
<div v-if="slots.left" class="sm-toolbar-column sm-toolbar-column-left">
|
||||||
<slot name="left"></slot>
|
<slot name="left"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm-toolbar-column sm-toolbar-column-right">
|
<div v-if="slots.default" class="sm-toolbar-column">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="slots.right"
|
||||||
|
class="sm-toolbar-column sm-toolbar-column-right">
|
||||||
<slot name="right"></slot>
|
<slot name="right"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSlots } from "vue";
|
||||||
|
|
||||||
|
const slots = useSlots();
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.sm-toolbar {
|
.sm-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -17,68 +28,41 @@
|
|||||||
|
|
||||||
.sm-toolbar-column {
|
.sm-toolbar-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
|
||||||
&.sm-toolbar-column-left {
|
&.sm-toolbar-column-left {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
& > * {
|
||||||
margin-bottom: 0;
|
margin: 0 map-get($spacer, 1);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// &.form-footer-column-left, &.form-footer-column-right {
|
|
||||||
// a, button {
|
|
||||||
// margin-left: map-get($spacer, 1);
|
|
||||||
// margin-right: map-get($spacer, 1);
|
|
||||||
|
|
||||||
// &:first-of-type {
|
|
||||||
// margin-left: 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// &:last-of-type {
|
|
||||||
// margin-right: 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
&.sm-toolbar-column-right {
|
&.sm-toolbar-column-right {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// @media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
// .form-footer {
|
.sm-toolbar {
|
||||||
// flex-direction: column-reverse;
|
.sm-toolbar-column {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
// .form-footer-column {
|
& > * {
|
||||||
// &.form-footer-column-left, &.form-footer-column-right {
|
margin: 0;
|
||||||
// display: flex;
|
}
|
||||||
// flex-direction: column-reverse;
|
}
|
||||||
// justify-content: center;
|
|
||||||
|
|
||||||
// & > * {
|
|
||||||
// display: block;
|
|
||||||
// width: 100%;
|
|
||||||
// text-align: center;
|
|
||||||
|
|
||||||
// margin-top: map-get($spacer, 1);
|
|
||||||
// margin-bottom: map-get($spacer, 1);
|
|
||||||
// margin-left: 0 !important;
|
|
||||||
// margin-right: 0 !important;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// &.form-footer-column-left {
|
|
||||||
// margin-bottom: -#{map-get($spacer, 1) / 2};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// &.form-footer-column-right {
|
|
||||||
// margin-top: -#{map-get($spacer, 1) / 2};
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface ApiOptions {
|
|||||||
export interface ApiResponse {
|
export interface ApiResponse {
|
||||||
status: number;
|
status: number;
|
||||||
message: string;
|
message: string;
|
||||||
data: Record<string, unknown>;
|
data: unknown;
|
||||||
json?: Record<string, unknown>;
|
json?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +84,13 @@ export const api = {
|
|||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
method: options.method || "GET",
|
method: options.method || "GET",
|
||||||
headers: options.headers,
|
headers: options.headers,
|
||||||
body: options.body,
|
|
||||||
signal: options.signal || null,
|
signal: options.signal || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof options.body == "string" && options.body.length > 0) {
|
||||||
|
fetchOptions.body = options.body;
|
||||||
|
}
|
||||||
|
|
||||||
const progressStore = useProgressStore();
|
const progressStore = useProgressStore();
|
||||||
progressStore.start();
|
progressStore.start();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
export interface Event {
|
export interface Event {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
hero: string;
|
||||||
|
content: string;
|
||||||
start_at: string;
|
start_at: string;
|
||||||
end_at: string;
|
end_at: string;
|
||||||
|
location: string;
|
||||||
|
address: string;
|
||||||
|
status: string;
|
||||||
|
registration_type: string;
|
||||||
|
registration_data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventResponse {
|
export interface EventResponse {
|
||||||
@@ -8,7 +17,7 @@ export interface EventResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EventCollection {
|
export interface EventCollection {
|
||||||
events: Event;
|
events: Event[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Media {
|
export interface Media {
|
||||||
@@ -50,6 +59,7 @@ export interface PostResponse {
|
|||||||
|
|
||||||
export interface PostCollection {
|
export interface PostCollection {
|
||||||
posts: Array<Post>;
|
posts: Array<Post>;
|
||||||
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ValidationResult,
|
ValidationResult,
|
||||||
} from "./validate";
|
} from "./validate";
|
||||||
|
|
||||||
type FormObjectValidateFunction = (item: string | null) => boolean;
|
type FormObjectValidateFunction = (item: string | null) => Promise<boolean>;
|
||||||
type FormObjectLoadingFunction = (state: boolean) => void;
|
type FormObjectLoadingFunction = (state: boolean) => void;
|
||||||
type FormObjectMessageFunction = (
|
type FormObjectMessageFunction = (
|
||||||
message?: string,
|
message?: string,
|
||||||
@@ -30,26 +30,27 @@ export interface FormObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultFormObject: FormObject = {
|
const defaultFormObject: FormObject = {
|
||||||
validate: function (item = null) {
|
validate: async function (item = null) {
|
||||||
const keys = item ? [item] : Object.keys(this.controls);
|
const keys = item ? [item] : Object.keys(this.controls);
|
||||||
let valid = true;
|
let valid = true;
|
||||||
|
|
||||||
keys.every(async (key) => {
|
await Promise.all(
|
||||||
if (
|
keys.map(async (key) => {
|
||||||
typeof this[key] == "object" &&
|
if (
|
||||||
Object.keys(this[key]).includes("validation")
|
typeof this.controls[key] == "object" &&
|
||||||
) {
|
Object.keys(this.controls[key]).includes("validation")
|
||||||
this[key].validation.result = await this[
|
) {
|
||||||
key
|
const validationResult = await this.controls[
|
||||||
].validation.validator.validate(this[key].value);
|
key
|
||||||
|
].validation.validator.validate(this.controls[key].value);
|
||||||
|
this.controls[key].validation.result = validationResult;
|
||||||
|
|
||||||
if (!this[key].validation.result.valid) {
|
if (!validationResult.valid) {
|
||||||
valid = false;
|
valid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
);
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,14 +37,6 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
component: () => import("@/views/ResetPassword.vue"),
|
component: () => import("@/views/ResetPassword.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/about",
|
|
||||||
name: "about",
|
|
||||||
meta: {
|
|
||||||
title: "About",
|
|
||||||
},
|
|
||||||
component: () => import("@/views/About.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/privacy",
|
path: "/privacy",
|
||||||
name: "privacy",
|
name: "privacy",
|
||||||
@@ -90,16 +82,16 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "workshop-list",
|
name: "event-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Workshops",
|
title: "Workshops",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/WorkshopList.vue"),
|
component: () => import("@/views/EventList.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id",
|
path: ":id",
|
||||||
name: "workshop-view",
|
name: "event-view",
|
||||||
component: () => import("@/views/WorkshopView.vue"),
|
component: () => import("@/views/EventView.vue"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -141,16 +133,16 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "news",
|
name: "post-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "News",
|
title: "News",
|
||||||
},
|
},
|
||||||
component: () => import("@/views/NewsList.vue"),
|
component: () => import("@/views/PostList.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":slug",
|
path: ":slug",
|
||||||
name: "post-view",
|
name: "post-view",
|
||||||
component: () => import("@/views/NewsView.vue"),
|
component: () => import("@/views/PostView.vue"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -171,7 +163,7 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "post-list",
|
name: "dashboard-post-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Posts",
|
title: "Posts",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -181,7 +173,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "create",
|
path: "create",
|
||||||
name: "post-create",
|
name: "dashboard-post-create",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Create Post",
|
title: "Create Post",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -191,7 +183,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id",
|
path: ":id",
|
||||||
name: "post-edit",
|
name: "dashboard-post-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Edit Post",
|
title: "Edit Post",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -206,7 +198,7 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "event-list",
|
name: "dashboard-event-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Events",
|
title: "Events",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -216,7 +208,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "create",
|
path: "create",
|
||||||
name: "event-create",
|
name: "dashboard-event-create",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Create Event",
|
title: "Create Event",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -226,7 +218,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id",
|
path: ":id",
|
||||||
name: "event-edit",
|
name: "dashboard-event-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Event Post",
|
title: "Event Post",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -238,7 +230,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "details",
|
path: "details",
|
||||||
name: "account-details",
|
name: "dashboard-account-details",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Account Details",
|
title: "Account Details",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -250,7 +242,7 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "user-list",
|
name: "dashboard-user-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Users",
|
title: "Users",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -260,7 +252,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id",
|
path: ":id",
|
||||||
name: "user-edit",
|
name: "dashboard-user-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Edit User",
|
title: "Edit User",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -275,7 +267,7 @@ export const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "media",
|
name: "dashboard-media",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Media",
|
title: "Media",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -285,7 +277,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "upload",
|
path: "upload",
|
||||||
name: "media-upload",
|
name: "dashboard-media-upload",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Upload Media",
|
title: "Upload Media",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -295,7 +287,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "edit/:id",
|
path: "edit/:id",
|
||||||
name: "media-edit",
|
name: "dashboard-media-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Edit Media",
|
title: "Edit Media",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
@@ -307,7 +299,7 @@ export const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "discord-bot-logs",
|
path: "discord-bot-logs",
|
||||||
name: "discord-bot-logs",
|
name: "dashboard-discord-bot-logs",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Discord Bot Logs",
|
title: "Discord Bot Logs",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage class="workshop-list">
|
<SMPage class="sm-workshop-list">
|
||||||
<template #container>
|
<template #container>
|
||||||
<h1>Workshops</h1>
|
<h1>Workshops</h1>
|
||||||
<div class="toolbar">
|
<SMToolbar>
|
||||||
<SMInput
|
<SMInput
|
||||||
v-model="filterKeywords"
|
v-model="filterKeywords"
|
||||||
label="Keywords"
|
label="Keywords"
|
||||||
@@ -17,21 +17,21 @@
|
|||||||
label="Date Range"
|
label="Date Range"
|
||||||
:feedback-invalid="dateRangeError"
|
:feedback-invalid="dateRangeError"
|
||||||
@change="handleFilter" />
|
@change="handleFilter" />
|
||||||
</div>
|
</SMToolbar>
|
||||||
<SMMessage
|
<SMMessage
|
||||||
v-if="formMessage.message"
|
v-if="formMessage"
|
||||||
:icon="formMessage.icon"
|
icon="alert-circle-outline"
|
||||||
:type="formMessage.type"
|
type="error"
|
||||||
:message="formMessage.message"
|
:message="formMessage"
|
||||||
class="mt-5" />
|
class="mt-5" />
|
||||||
<SMPanelList
|
<SMPanelList
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:not-found="events.value?.length == 0"
|
:not-found="events.length == 0"
|
||||||
not-found-text="No workshops found">
|
not-found-text="No workshops found">
|
||||||
<SMPanel
|
<SMPanel
|
||||||
v-for="event in events.value"
|
v-for="event in events"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
:to="{ name: 'workshop-view', params: { id: event.id } }"
|
:to="{ name: 'event-view', params: { id: event.id } }"
|
||||||
:title="event.title"
|
:title="event.title"
|
||||||
:image="event.hero"
|
:image="event.hero"
|
||||||
:show-time="true"
|
:show-time="true"
|
||||||
@@ -54,30 +54,25 @@ import SMInput from "../components/SMInput.vue";
|
|||||||
import SMMessage from "../components/SMMessage.vue";
|
import SMMessage from "../components/SMMessage.vue";
|
||||||
import SMPanel from "../components/SMPanel.vue";
|
import SMPanel from "../components/SMPanel.vue";
|
||||||
import SMPanelList from "../components/SMPanelList.vue";
|
import SMPanelList from "../components/SMPanelList.vue";
|
||||||
|
import SMToolbar from "../components/SMToolbar.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
|
import { Event, EventCollection } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const events = reactive([]);
|
let events: Event[] = reactive([]);
|
||||||
const dateRangeError = ref("");
|
const dateRangeError = ref("");
|
||||||
|
|
||||||
const formMessage = reactive({
|
const formMessage = ref("");
|
||||||
icon: "",
|
|
||||||
type: "",
|
|
||||||
message: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const filterKeywords = ref("");
|
const filterKeywords = ref("");
|
||||||
const filterLocation = ref("");
|
const filterLocation = ref("");
|
||||||
const filterDateRange = ref("");
|
const filterDateRange = ref("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load page data.
|
||||||
|
*/
|
||||||
const handleLoad = async () => {
|
const handleLoad = async () => {
|
||||||
formMessage.type = "error";
|
|
||||||
formMessage.icon = "alert-circle-outline";
|
|
||||||
formMessage.message = "";
|
|
||||||
|
|
||||||
events.value = [];
|
|
||||||
|
|
||||||
let query = {};
|
let query = {};
|
||||||
query["limit"] = 10;
|
query["limit"] = 10;
|
||||||
|
|
||||||
@@ -111,11 +106,16 @@ const handleLoad = async () => {
|
|||||||
dateRangeError.value = "";
|
dateRangeError.value = "";
|
||||||
} else {
|
} else {
|
||||||
dateRangeError.value = "Invalid date range";
|
dateRangeError.value = "Invalid date range";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dateRangeError.value = "";
|
dateRangeError.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
formMessage.value = "";
|
||||||
|
events = [];
|
||||||
|
|
||||||
if (Object.keys(query).length == 1 && Object.keys(query)[0] == "limit") {
|
if (Object.keys(query).length == 1 && Object.keys(query)[0] == "limit") {
|
||||||
query["end_at"] =
|
query["end_at"] =
|
||||||
">" +
|
">" +
|
||||||
@@ -127,10 +127,12 @@ const handleLoad = async () => {
|
|||||||
params: query,
|
params: query,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.data.events) {
|
const data = result.data as EventCollection;
|
||||||
events.value = result.data.events;
|
|
||||||
|
|
||||||
events.value.forEach((item) => {
|
if (data && data.events) {
|
||||||
|
events = data.events;
|
||||||
|
|
||||||
|
events.forEach((item) => {
|
||||||
item.start_at = new SMDate(item.start_at, {
|
item.start_at = new SMDate(item.start_at, {
|
||||||
format: "yyyy-MM-dd HH:mm:ss",
|
format: "yyyy-MM-dd HH:mm:ss",
|
||||||
utc: true,
|
utc: true,
|
||||||
@@ -145,7 +147,7 @@ const handleLoad = async () => {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.status != 404) {
|
if (error.status != 404) {
|
||||||
formMessage.message =
|
formMessage.value =
|
||||||
error.response?.data?.message ||
|
error.response?.data?.message ||
|
||||||
"Could not load any events from the server.";
|
"Could not load any events from the server.";
|
||||||
}
|
}
|
||||||
@@ -156,7 +158,6 @@ const handleLoad = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFilter = async () => {
|
const handleFilter = async () => {
|
||||||
loading.value = true;
|
|
||||||
handleLoad();
|
handleLoad();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,7 +189,7 @@ handleLoad();
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.workshop-list .toolbar {
|
.sm-workshop-list .toolbar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage :full="true" :loading="imageUrl.length == 0" class="workshop-view">
|
<SMPage
|
||||||
|
:full="true"
|
||||||
|
:loading="imageUrl.length == 0"
|
||||||
|
class="sm-workshop-view"
|
||||||
|
:error="pageError">
|
||||||
<div
|
<div
|
||||||
class="workshop-image"
|
class="sm-workshop-image"
|
||||||
:style="{ backgroundImage: `url('${imageUrl}')` }"></div>
|
:style="{ backgroundImage: `url('${imageUrl}')` }"></div>
|
||||||
<SMContainer>
|
<SMContainer>
|
||||||
<SMMessage
|
<SMMessage
|
||||||
v-if="formMessage.message"
|
v-if="formMessage"
|
||||||
:icon="formMessage.icon"
|
icon="alert-circle-outline"
|
||||||
:type="formMessage.type"
|
type="error"
|
||||||
:message="formMessage.message"
|
:message="formMessage"
|
||||||
class="mt-5" />
|
class="mt-5" />
|
||||||
<SMContainer class="workshop-page">
|
<SMContainer class="sm-workshop-page">
|
||||||
<div class="workshop-body">
|
<div class="sm-workshop-body">
|
||||||
<h2 class="workshop-title">{{ event.title }}</h2>
|
<h2 class="sm-workshop-title">{{ event.title }}</h2>
|
||||||
<SMHTML :html="event.content" class="workshop-content" />
|
<SMHTML :html="event.content" class="sm-workshop-content" />
|
||||||
</div>
|
</div>
|
||||||
<div class="workshop-info">
|
<div class="sm-workshop-info">
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
event.status == 'closed' ||
|
event.status == 'closed' ||
|
||||||
@@ -24,17 +28,17 @@
|
|||||||
format: 'ymd',
|
format: 'ymd',
|
||||||
}).isBefore())
|
}).isBefore())
|
||||||
"
|
"
|
||||||
class="workshop-registration workshop-registration-closed">
|
class="sm-workshop-registration sm-workshop-registration-closed">
|
||||||
Registration for this event has closed.
|
Registration for this event has closed.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="event.status == 'soon'"
|
v-if="event.status == 'soon'"
|
||||||
class="workshop-registration workshop-registration-soon">
|
class="sm-workshop-registration sm-workshop-registration-soon">
|
||||||
Registration for this event will open soon.
|
Registration for this event will open soon.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="event.status == 'cancelled'"
|
v-if="event.status == 'cancelled'"
|
||||||
class="workshop-registration workshop-registration-cancelled">
|
class="sm-workshop-registration sm-workshop-registration-cancelled">
|
||||||
This event has been cancelled.
|
This event has been cancelled.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -45,7 +49,7 @@
|
|||||||
}).isAfter() &&
|
}).isAfter() &&
|
||||||
event.registration_type == 'none'
|
event.registration_type == 'none'
|
||||||
"
|
"
|
||||||
class="workshop-registration workshop-registration-none">
|
class="sm-workshop-registration sm-workshop-registration-none">
|
||||||
Registration not required for this event.<br />Arrive
|
Registration not required for this event.<br />Arrive
|
||||||
early to avoid disappointment as seating maybe limited.
|
early to avoid disappointment as seating maybe limited.
|
||||||
</div>
|
</div>
|
||||||
@@ -57,12 +61,13 @@
|
|||||||
}).isAfter() &&
|
}).isAfter() &&
|
||||||
event.registration_type != 'none'
|
event.registration_type != 'none'
|
||||||
"
|
"
|
||||||
class="workshop-registration workshop-registration-url">
|
class="sm-workshop-registration sm-workshop-registration-url">
|
||||||
<SMButton
|
<SMButton
|
||||||
:href="registerUrl"
|
:href="registerUrl"
|
||||||
|
:block="true"
|
||||||
label="Register for Event"></SMButton>
|
label="Register for Event"></SMButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="workshop-date">
|
<div class="sm-workshop-date">
|
||||||
<h4><ion-icon name="calendar-outline" />Date / Time</h4>
|
<h4><ion-icon name="calendar-outline" />Date / Time</h4>
|
||||||
<p
|
<p
|
||||||
v-for="(line, index) in workshopDate"
|
v-for="(line, index) in workshopDate"
|
||||||
@@ -71,7 +76,7 @@
|
|||||||
{{ line }}
|
{{ line }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="workshop-location">
|
<div class="sm-workshop-location">
|
||||||
<h4><ion-icon name="location-outline" />Location</h4>
|
<h4><ion-icon name="location-outline" />Location</h4>
|
||||||
<p>
|
<p>
|
||||||
{{
|
{{
|
||||||
@@ -88,27 +93,37 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref } from "vue";
|
import { computed, Ref, ref } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import SMButton from "../components/SMButton.vue";
|
import SMButton from "../components/SMButton.vue";
|
||||||
import SMHTML from "../components/SMHTML.vue";
|
import SMHTML from "../components/SMHTML.vue";
|
||||||
import SMMessage from "../components/SMMessage.vue";
|
import SMMessage from "../components/SMMessage.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
|
import { Event, EventResponse, MediaResponse } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
|
import { imageLoad } from "../helpers/image";
|
||||||
import { useApplicationStore } from "../store/ApplicationStore";
|
import { useApplicationStore } from "../store/ApplicationStore";
|
||||||
|
|
||||||
import { ApiEvent, ApiMedia } from "../helpers/api.types";
|
|
||||||
import { imageLoad } from "../helpers/image";
|
|
||||||
|
|
||||||
const applicationStore = useApplicationStore();
|
const applicationStore = useApplicationStore();
|
||||||
const event = ref({});
|
|
||||||
|
/**
|
||||||
|
* Event data
|
||||||
|
*/
|
||||||
|
const event: Ref<Event | null> = ref(null);
|
||||||
|
|
||||||
const imageUrl = ref("");
|
const imageUrl = ref("");
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const formMessage = reactive({
|
|
||||||
icon: "",
|
/**
|
||||||
type: "",
|
* Page message.
|
||||||
message: "",
|
*/
|
||||||
});
|
const formMessage = ref("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page error.
|
||||||
|
*/
|
||||||
|
let pageError = 200;
|
||||||
|
|
||||||
const workshopDate = computed(() => {
|
const workshopDate = computed(() => {
|
||||||
let str: string[] = [];
|
let str: string[] = [];
|
||||||
@@ -166,23 +181,23 @@ const registerUrl = computed(() => {
|
|||||||
return href;
|
return href;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the page data.
|
||||||
|
*/
|
||||||
const handleLoad = async () => {
|
const handleLoad = async () => {
|
||||||
formMessage.type = "error";
|
formMessage.value = "";
|
||||||
formMessage.icon = "alert-circle-outline";
|
|
||||||
formMessage.message = "";
|
|
||||||
|
|
||||||
api.get(`/events/${route.params.id}`)
|
api.get({
|
||||||
|
url: "/events/{event}",
|
||||||
|
params: {
|
||||||
|
event: route.params.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
event.value =
|
const eventData = result.data as EventResponse;
|
||||||
result.data &&
|
|
||||||
(result.data as ApiEvent).event &&
|
|
||||||
Object.keys((result.data as ApiEvent).event).length > 0
|
|
||||||
? (result.data as ApiEvent).event
|
|
||||||
: {};
|
|
||||||
|
|
||||||
if (event.value) {
|
|
||||||
// event.value = result.data.event as ApiEventItem;
|
|
||||||
|
|
||||||
|
if (eventData && eventData.event) {
|
||||||
|
event.value = eventData.event;
|
||||||
event.value.start_at = new SMDate(event.value.start_at, {
|
event.value.start_at = new SMDate(event.value.start_at, {
|
||||||
format: "ymd",
|
format: "ymd",
|
||||||
utc: true,
|
utc: true,
|
||||||
@@ -195,53 +210,46 @@ const handleLoad = async () => {
|
|||||||
applicationStore.setDynamicTitle(event.value.title);
|
applicationStore.setDynamicTitle(event.value.title);
|
||||||
handleLoadImage();
|
handleLoadImage();
|
||||||
} else {
|
} else {
|
||||||
formMessage.message =
|
pageError = 404;
|
||||||
"Could not load event information from the server.";
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
formMessage.message =
|
formMessage.value =
|
||||||
error.data?.message ||
|
error.data?.message ||
|
||||||
"Could not load event information from the server.";
|
"Could not load event information from the server.";
|
||||||
});
|
});
|
||||||
|
|
||||||
// try {
|
|
||||||
// const result = await api.get(`/events/${route.params.id}`);
|
|
||||||
// event.value = result.data.event as ApiEventItem;
|
|
||||||
|
|
||||||
// event.value.start_at = timestampUtcToLocal(event.value.start_at);
|
|
||||||
// event.value.end_at = timestampUtcToLocal(event.value.end_at);
|
|
||||||
|
|
||||||
// applicationStore.setDynamicTitle(event.value.title);
|
|
||||||
// handleLoadImage();
|
|
||||||
// } catch (error) {
|
|
||||||
// formMessage.message =
|
|
||||||
// error.data?.message ||
|
|
||||||
// "Could not load event information from the server.";
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the hero image.
|
||||||
|
*/
|
||||||
const handleLoadImage = async () => {
|
const handleLoadImage = async () => {
|
||||||
try {
|
api.get({
|
||||||
const result = await api.get(`/media/${event.value.hero}`);
|
url: "/media/{medium}",
|
||||||
const data = result.data as ApiMedia;
|
params: {
|
||||||
|
medium: event.value.hero,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
const data = result.data as MediaResponse;
|
||||||
|
|
||||||
if (data && data.medium) {
|
if (data && data.medium) {
|
||||||
imageLoad(data.medium.url, (url) => {
|
imageLoad(data.medium.url, (url) => {
|
||||||
imageUrl.value = url;
|
imageUrl.value = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
})
|
||||||
/* empty */
|
.catch(() => {
|
||||||
}
|
/* empty */
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleLoad();
|
handleLoad();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workshop-view {
|
.sm-workshop-view {
|
||||||
.workshop-image {
|
.sm-workshop-image {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -253,27 +261,27 @@ handleLoad();
|
|||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
transition: background-image 0.2s;
|
transition: background-image 0.2s;
|
||||||
|
|
||||||
.workshop-image-loader {
|
.sm-workshop-image-loader {
|
||||||
font-size: 5rem;
|
font-size: 5rem;
|
||||||
color: $secondary-color;
|
color: $secondary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-page {
|
.sm-workshop-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.workshop-body,
|
.sm-workshop-body,
|
||||||
.workshop-info {
|
.sm-workshop-info {
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-body {
|
.sm-workshop-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-info {
|
.sm-workshop-info {
|
||||||
width: 18rem;
|
width: 18rem;
|
||||||
margin-left: 2rem;
|
margin-left: 2rem;
|
||||||
|
|
||||||
@@ -296,17 +304,13 @@ handleLoad();
|
|||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-registration {
|
.sm-workshop-registration {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
|
|
||||||
.button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-registration-none,
|
.sm-workshop-registration-none,
|
||||||
.workshop-registration-soon {
|
.sm-workshop-registration-soon {
|
||||||
border: 1px solid #ffeeba;
|
border: 1px solid #ffeeba;
|
||||||
background-color: #fff3cd;
|
background-color: #fff3cd;
|
||||||
color: #856404;
|
color: #856404;
|
||||||
@@ -315,8 +319,8 @@ handleLoad();
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-registration-closed,
|
.sm-workshop-registration-closed,
|
||||||
.workshop-registration-cancelled {
|
.sm-workshop-registration-cancelled {
|
||||||
border: 1px solid #f5c2c7;
|
border: 1px solid #f5c2c7;
|
||||||
background-color: #f8d7da;
|
background-color: #f8d7da;
|
||||||
color: #842029;
|
color: #842029;
|
||||||
@@ -325,8 +329,8 @@ handleLoad();
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-date,
|
.sm-workshop-date,
|
||||||
.workshop-location {
|
.sm-workshop-location {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,14 +338,14 @@ handleLoad();
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.workshop-view .workshop-page {
|
.sm-workshop-view .sm-workshop-page {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.workshop-body {
|
.sm-workshop-body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workshop-info {
|
.sm-workshop-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
skills that they can use throughout their lives.
|
skills that they can use throughout their lives.
|
||||||
</p>
|
</p>
|
||||||
<SMButton
|
<SMButton
|
||||||
:to="{ name: 'workshop-list' }"
|
:to="{ name: 'event-list' }"
|
||||||
label="Explore Workshops" />
|
label="Explore Workshops" />
|
||||||
</SMColumn>
|
</SMColumn>
|
||||||
<SMColumn
|
<SMColumn
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
as well as updates on upcoming workshops.
|
as well as updates on upcoming workshops.
|
||||||
</p>
|
</p>
|
||||||
<SMDialog class="p-0" no-shadow>
|
<SMDialog class="p-0" no-shadow>
|
||||||
<SMForm v-model="form" @submit.prevent="handleSubscribe">
|
<SMForm v-model="form" @submit="handleSubscribe">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<SMInput control="email" />
|
<SMInput control="email" />
|
||||||
<SMButton type="submit" label="Subscribe" />
|
<SMButton type="submit" label="Subscribe" />
|
||||||
@@ -133,6 +133,7 @@ import SMForm from "../components/SMForm.vue";
|
|||||||
import SMInput from "../components/SMInput.vue";
|
import SMInput from "../components/SMInput.vue";
|
||||||
|
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
|
import { EventCollection, PostCollection } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
import { Form, FormControl } from "../helpers/form";
|
import { Form, FormControl } from "../helpers/form";
|
||||||
import { excerpt } from "../helpers/string";
|
import { excerpt } from "../helpers/string";
|
||||||
@@ -156,47 +157,55 @@ const handleLoad = async () => {
|
|||||||
params: {
|
params: {
|
||||||
limit: 3,
|
limit: 3,
|
||||||
},
|
},
|
||||||
}).then((response) => {
|
})
|
||||||
if (response.data.posts) {
|
.then((result) => {
|
||||||
response.data.posts.forEach((post) => {
|
const data = result.data as PostCollection;
|
||||||
posts.push({
|
|
||||||
title: post.title,
|
|
||||||
content: excerpt(post.content, 200),
|
|
||||||
image: post.hero,
|
|
||||||
url: { name: "post-view", params: { slug: post.slug } },
|
|
||||||
cta: "Read More...",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
if (data && data.posts) {
|
||||||
let result = await api.get({
|
data.posts.forEach((post) => {
|
||||||
url: "/events",
|
posts.push({
|
||||||
params: {
|
title: post.title,
|
||||||
limit: 3,
|
content: excerpt(post.content, 200),
|
||||||
end_at:
|
image: post.hero,
|
||||||
">" +
|
url: { name: "post-view", params: { slug: post.slug } },
|
||||||
new SMDate("now").format("yyyy-MM-dd HH:mm:ss", {
|
cta: "Read More...",
|
||||||
utc: true,
|
});
|
||||||
}),
|
});
|
||||||
},
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.data.events) {
|
api.get({
|
||||||
result.data.events.forEach((event) => {
|
url: "/events",
|
||||||
events.push({
|
params: {
|
||||||
title: event.title,
|
limit: 3,
|
||||||
content: excerpt(event.content, 200),
|
end_at:
|
||||||
image: event.hero,
|
">" +
|
||||||
url: { name: "workshop-view", params: { id: event.id } },
|
new SMDate("now").format("yyyy-MM-dd HH:mm:ss", {
|
||||||
cta: "View Workshop",
|
utc: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
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 (error) {
|
.catch(() => {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
});
|
||||||
|
|
||||||
for (let i = 1; i <= Math.max(posts.length, events.length); i++) {
|
for (let i = 1; i <= Math.max(posts.length, events.length); i++) {
|
||||||
if (i <= posts.length) {
|
if (i <= posts.length) {
|
||||||
@@ -219,12 +228,12 @@ const handleSubscribe = async () => {
|
|||||||
await api.post({
|
await api.post({
|
||||||
url: "/subscriptions",
|
url: "/subscriptions",
|
||||||
body: {
|
body: {
|
||||||
email: form.email.value,
|
email: form.controls.email.value,
|
||||||
captcha_token: captcha,
|
captcha_token: captcha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
form.email.value = "";
|
form.controls.email.value = "";
|
||||||
form.message("Your email address has been subscribed.", "success");
|
form.message("Your email address has been subscribed.", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
form.apiErrors(err);
|
form.apiErrors(err);
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ const form = reactive(
|
|||||||
|
|
||||||
const redirectQuery = useRoute().query.redirect;
|
const redirectQuery = useRoute().query.redirect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user submitting the login form.
|
||||||
|
*/
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
form.message();
|
form.message();
|
||||||
form.loading(true);
|
form.loading(true);
|
||||||
@@ -70,7 +73,7 @@ const handleSubmit = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const login = result.data as unknown as LoginResponse;
|
const login = result.data as LoginResponse;
|
||||||
|
|
||||||
userStore.setUserDetails(login.user);
|
userStore.setUserDetails(login.user);
|
||||||
userStore.setUserToken(login.token);
|
userStore.setUserToken(login.token);
|
||||||
@@ -84,6 +87,7 @@ const handleSubmit = async () => {
|
|||||||
router.push({ name: "dashboard" });
|
router.push({ name: "dashboard" });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
form.controls.password.value = "";
|
||||||
form.apiErrors(err);
|
form.apiErrors(err);
|
||||||
} finally {
|
} finally {
|
||||||
form.loading(false);
|
form.loading(false);
|
||||||
|
|||||||
@@ -1,49 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage no-breadcrumbs background="/img/background.jpg">
|
<SMPage no-breadcrumbs background="/img/background.jpg">
|
||||||
<SMRow>
|
<SMLoader :loading="true" />
|
||||||
<SMDialog narrow class="mt-5" :loading="formLoading">
|
|
||||||
<h1>Logged out</h1>
|
|
||||||
<SMRow>
|
|
||||||
<SMColumn class="justify-content-center">
|
|
||||||
<p class="mt-0 text-center">
|
|
||||||
You have now been logged out
|
|
||||||
</p>
|
|
||||||
</SMColumn>
|
|
||||||
</SMRow>
|
|
||||||
<SMRow>
|
|
||||||
<SMColumn class="justify-content-center">
|
|
||||||
<SMButton :to="{ name: 'home' }" label="Home" />
|
|
||||||
</SMColumn>
|
|
||||||
</SMRow>
|
|
||||||
</SMDialog>
|
|
||||||
</SMRow>
|
|
||||||
</SMPage>
|
</SMPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { useRouter } from "vue-router";
|
||||||
|
import SMLoader from "../components/SMLoader.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
|
import { useToastStore } from "../store/ToastStore";
|
||||||
import { useUserStore } from "../store/UserStore";
|
import { useUserStore } from "../store/UserStore";
|
||||||
|
|
||||||
import SMButton from "../components/SMButton.vue";
|
const router = useRouter();
|
||||||
import SMDialog from "../components/SMDialog.vue";
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const formLoading = ref(false);
|
const toastStore = useToastStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout the current user and redirect to home page.
|
||||||
|
*/
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
formLoading.value = true;
|
api.post({
|
||||||
|
url: "/logout",
|
||||||
try {
|
}).finally(() => {
|
||||||
await api.post({
|
userStore.clearUser();
|
||||||
url: "/logout",
|
toastStore.addToast({
|
||||||
|
title: "Logged Out",
|
||||||
|
content: "You have been logged out.",
|
||||||
|
type: "success",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
router.push({ name: "home" });
|
||||||
console.log(err);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
userStore.clearUser();
|
|
||||||
formLoading.value = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logout();
|
logout();
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMContainer class="rules">
|
<SMPage class="sm-minecraft">
|
||||||
<h1>Connecting to our Minecraft Server</h1>
|
<template #container>
|
||||||
<ol>
|
<h1>Connecting to our Minecraft Server</h1>
|
||||||
<li>
|
<ol>
|
||||||
Open up your Minecraft on your computer (Java) or tablet
|
<li>
|
||||||
(Bedrock) and make sure you are using version 1.19.3
|
Open up your Minecraft on your computer (Java) or tablet
|
||||||
</li>
|
(Bedrock) and make sure you are using version 1.19.3
|
||||||
<li>Click Multiplayer</li>
|
</li>
|
||||||
<li>Click Add Server</li>
|
<li>Click Multiplayer</li>
|
||||||
<li>Enter Server Name STEMMechanics</li>
|
<li>Click Add Server</li>
|
||||||
<li>Enter Server Address mc.stemmech.com.au</li>
|
<li>Enter Server Name STEMMechanics</li>
|
||||||
<li>
|
<li>Enter Server Address mc.stemmech.com.au</li>
|
||||||
We have a custom resourcepack which you can enable before
|
<li>
|
||||||
joining
|
We have a custom resourcepack which you can enable before
|
||||||
</li>
|
joining
|
||||||
<li>Click Done</li>
|
</li>
|
||||||
<li>Join the Server!</li>
|
<li>Click Done</li>
|
||||||
</ol>
|
<li>Join the Server!</li>
|
||||||
<h2>Goodbye Drustcraft</h2>
|
</ol>
|
||||||
<p>
|
<h2>Goodbye Drustcraft</h2>
|
||||||
STEMMechanics launched the Drustcraft server three years ago and
|
<p>
|
||||||
since then, players have had countless enjoyable experiences. Cities
|
STEMMechanics launched the Drustcraft server three years ago and
|
||||||
were built, bosses defeated, and most importantly, a tight-knit
|
since then, players have had countless enjoyable experiences.
|
||||||
community formed.
|
Cities were built, bosses defeated, and most importantly, a
|
||||||
</p>
|
tight-knit community formed.
|
||||||
<p>
|
</p>
|
||||||
Maintaining the server design became overwhelming and took away the
|
<p>
|
||||||
fun of playing Minecraft. Hence, in January, the decision was made
|
Maintaining the server design became overwhelming and took away
|
||||||
to shut down Drustcraft and offer a more straightforward Minecraft
|
the fun of playing Minecraft. Hence, in January, the decision
|
||||||
server, retaining the beloved elements of Drustcraft like
|
was made to shut down Drustcraft and offer a more
|
||||||
mini-games, bosses, and survival. Join us on the new STEMMechanics
|
straightforward Minecraft server, retaining the beloved elements
|
||||||
Minecraft server, where the Drustcraft community awaits.
|
of Drustcraft like mini-games, bosses, and survival. Join us on
|
||||||
</p>
|
the new STEMMechanics Minecraft server, where the Drustcraft
|
||||||
</SMContainer>
|
community awaits.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</SMPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.rules {
|
.sm-minecraft {
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SMPage
|
|
||||||
:loading="pageLoading"
|
|
||||||
full
|
|
||||||
class="page-post-view"
|
|
||||||
:page-error="error">
|
|
||||||
<div
|
|
||||||
class="heading-image"
|
|
||||||
:style="{
|
|
||||||
backgroundImage: `url('${post.hero_url}')`,
|
|
||||||
}"></div>
|
|
||||||
<SMContainer>
|
|
||||||
<div class="heading-info">
|
|
||||||
<h1>{{ post.title }}</h1>
|
|
||||||
<div class="date-author">
|
|
||||||
<ion-icon name="calendar-outline" />
|
|
||||||
{{ formattedPublishAt(post.publish_at) }}, by
|
|
||||||
{{ post.user_username }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<component :is="formattedContent" ref="content"></component>
|
|
||||||
<SMAttachments :attachments="post.attachments || []" />
|
|
||||||
</SMContainer>
|
|
||||||
</SMPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { api } from "../helpers/api";
|
|
||||||
import { SMDate } from "../helpers/datetime";
|
|
||||||
import { useApplicationStore } from "../store/ApplicationStore";
|
|
||||||
|
|
||||||
import SMAttachments from "../components/SMAttachments.vue";
|
|
||||||
|
|
||||||
const applicationStore = useApplicationStore();
|
|
||||||
const route = useRoute();
|
|
||||||
let post = ref({});
|
|
||||||
let content = ref(null);
|
|
||||||
let error = ref(0);
|
|
||||||
let pageLoading = ref(true);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
if (route.params.slug) {
|
|
||||||
try {
|
|
||||||
let res = await api.get({
|
|
||||||
url: "/posts",
|
|
||||||
params: {
|
|
||||||
slug: `=${route.params.slug}`,
|
|
||||||
limit: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.data.posts) {
|
|
||||||
error.value = 500;
|
|
||||||
} else {
|
|
||||||
if (res.data.total == 0) {
|
|
||||||
error.value = 404;
|
|
||||||
} else {
|
|
||||||
post.value = res.data.posts[0];
|
|
||||||
|
|
||||||
post.value.publish_at = new SMDate(post.value.publish_at, {
|
|
||||||
format: "ymd",
|
|
||||||
utc: true,
|
|
||||||
}).format("yyyy/MM/dd HH:mm:ss");
|
|
||||||
|
|
||||||
applicationStore.setDynamicTitle(post.value.title);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = await api.get({
|
|
||||||
url: `/media/${post.value.hero}`,
|
|
||||||
});
|
|
||||||
post.value.hero_url = result.data.medium.url;
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = await api.get({
|
|
||||||
url: `/users/${post.value.user_id}`,
|
|
||||||
});
|
|
||||||
post.value.user_username = result.data.user.username;
|
|
||||||
} catch (error) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error.value = 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pageLoading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formattedPublishAt = (dateStr) => {
|
|
||||||
return new SMDate(dateStr, { format: "yMd" }).format("MMMM d, yyyy");
|
|
||||||
};
|
|
||||||
|
|
||||||
const formattedContent = computed(() => {
|
|
||||||
let html = post.value.content;
|
|
||||||
if (html) {
|
|
||||||
const regex = new RegExp(
|
|
||||||
`<a ([^>]*?)href="${import.meta.env.APP_URL}(.*?>.*?)</a>`,
|
|
||||||
"ig"
|
|
||||||
);
|
|
||||||
html = html.replace(regex, '<router-link $1to="$2</router-link>');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
template: `<div class="content">${html}</div>`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
loadData();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.page-post-view {
|
|
||||||
.heading-image {
|
|
||||||
background-color: #eee;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
height: 15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading-info {
|
|
||||||
padding: 0 map-get($spacer, 3);
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: left;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-author {
|
|
||||||
font-size: 80%;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
margin-top: map-get($spacer, 4);
|
|
||||||
padding: 0 map-get($spacer, 3);
|
|
||||||
|
|
||||||
a span {
|
|
||||||
color: $primary-color !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
|
||||||
.page-post-view .heading-image {
|
|
||||||
height: 10rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage class="news-list">
|
<SMPage class="sm-post-list" :loading="pageLoading">
|
||||||
<template #container>
|
<template #container>
|
||||||
<SMMessage
|
<SMMessage
|
||||||
v-if="message"
|
v-if="message"
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
:message="message"
|
:message="message"
|
||||||
class="mt-5" />
|
class="mt-5" />
|
||||||
<SMPanelList
|
<SMPanelList
|
||||||
:loading="loading"
|
:not-found="!pageLoading && posts.length == 0"
|
||||||
:not-found="!loading && posts.length == 0"
|
|
||||||
not-found-text="No news found">
|
not-found-text="No news found">
|
||||||
<SMPanel
|
<SMPanel
|
||||||
v-for="post in posts"
|
v-for="post in posts"
|
||||||
@@ -23,37 +22,51 @@
|
|||||||
button="Read More"
|
button="Read More"
|
||||||
button-type="outline" />
|
button-type="outline" />
|
||||||
</SMPanelList>
|
</SMPanelList>
|
||||||
|
<SMPagination
|
||||||
|
v-model="postsPage"
|
||||||
|
:total="postsTotal"
|
||||||
|
:per-page="postsPerPage" />
|
||||||
</template>
|
</template>
|
||||||
</SMPage>
|
</SMPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, ref } from "vue";
|
import { Ref, ref, watch } from "vue";
|
||||||
import SMMessage from "../components/SMMessage.vue";
|
import SMMessage from "../components/SMMessage.vue";
|
||||||
|
import SMPagination from "../components/SMPagination.vue";
|
||||||
import SMPanel from "../components/SMPanel.vue";
|
import SMPanel from "../components/SMPanel.vue";
|
||||||
import SMPanelList from "../components/SMPanelList.vue";
|
import SMPanelList from "../components/SMPanelList.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
|
|
||||||
import { Post, PostCollection } from "../helpers/api.types";
|
import { Post, PostCollection } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
|
|
||||||
const message = ref("");
|
const message = ref("");
|
||||||
const loading = ref(true);
|
const pageLoading = ref(true);
|
||||||
const posts: Ref<Post[]> = ref([]);
|
const posts: Ref<Post[]> = ref([]);
|
||||||
|
|
||||||
const handleLoad = async () => {
|
const postsPerPage = 9;
|
||||||
|
let postsPage = ref(1);
|
||||||
|
let postsTotal = ref(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the page data.
|
||||||
|
*/
|
||||||
|
const handleLoad = () => {
|
||||||
message.value = "";
|
message.value = "";
|
||||||
|
pageLoading.value = true;
|
||||||
|
|
||||||
api.get({
|
api.get({
|
||||||
url: "/posts",
|
url: "/posts",
|
||||||
params: {
|
params: {
|
||||||
limit: 5,
|
limit: postsPerPage,
|
||||||
|
page: postsPage.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const data = result.data as PostCollection;
|
const data = result.data as PostCollection;
|
||||||
|
|
||||||
posts.value = data.posts;
|
posts.value = data.posts;
|
||||||
|
postsTotal.value = data.total;
|
||||||
posts.value.forEach((post) => {
|
posts.value.forEach((post) => {
|
||||||
post.publish_at = new SMDate(post.publish_at, {
|
post.publish_at = new SMDate(post.publish_at, {
|
||||||
format: "ymd",
|
format: "ymd",
|
||||||
@@ -62,13 +75,23 @@ const handleLoad = async () => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
message.value =
|
if (error.status != 404) {
|
||||||
error.data?.message || "The server is currently not available";
|
message.value =
|
||||||
|
error.data?.message ||
|
||||||
|
"The server is currently not available";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false;
|
pageLoading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => postsPage.value,
|
||||||
|
() => {
|
||||||
|
handleLoad();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
handleLoad();
|
handleLoad();
|
||||||
</script>
|
</script>
|
||||||
180
resources/js/views/PostView.vue
Normal file
180
resources/js/views/PostView.vue
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<SMPage
|
||||||
|
:loading="pageLoading"
|
||||||
|
full
|
||||||
|
class="sm-page-post-view"
|
||||||
|
:page-error="pageError">
|
||||||
|
<div class="sm-heading-image" :style="styleObject"></div>
|
||||||
|
<SMContainer>
|
||||||
|
<div class="sm-heading-info">
|
||||||
|
<h1>{{ post.title }}</h1>
|
||||||
|
<div class="sm-date-author">
|
||||||
|
<ion-icon name="calendar-outline" />
|
||||||
|
{{ formattedPublishAt(post.publish_at) }}, by
|
||||||
|
{{ postUser.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SMHTML :html="post.content" />
|
||||||
|
<SMAttachments :attachments="post.attachments || []" />
|
||||||
|
</SMContainer>
|
||||||
|
</SMPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, Ref } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import SMAttachments from "../components/SMAttachments.vue";
|
||||||
|
import SMHTML from "../components/SMHTML.vue";
|
||||||
|
import { api } from "../helpers/api";
|
||||||
|
import {
|
||||||
|
MediaResponse,
|
||||||
|
Post,
|
||||||
|
PostCollection,
|
||||||
|
User,
|
||||||
|
UserResponse,
|
||||||
|
} from "../helpers/api.types";
|
||||||
|
import { SMDate } from "../helpers/datetime";
|
||||||
|
import { useApplicationStore } from "../store/ApplicationStore";
|
||||||
|
|
||||||
|
const applicationStore = useApplicationStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The post data.
|
||||||
|
*/
|
||||||
|
let post: Ref<Post> = ref(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current page error.
|
||||||
|
*/
|
||||||
|
let pageError = ref(200);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the page loading.
|
||||||
|
*/
|
||||||
|
let pageLoading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post styles.
|
||||||
|
*/
|
||||||
|
let styleObject = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post user.
|
||||||
|
*/
|
||||||
|
let postUser: User | null = null;
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
let slug = useRoute().params.slug || "";
|
||||||
|
|
||||||
|
if (slug.length > 0) {
|
||||||
|
pageLoading.value = true;
|
||||||
|
|
||||||
|
api.get({
|
||||||
|
url: "/posts/",
|
||||||
|
params: {
|
||||||
|
slug: `=${slug}`,
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
}).then((result) => {
|
||||||
|
const data = result.data as PostCollection;
|
||||||
|
|
||||||
|
if (data && data.posts && data.total && data.total > 0) {
|
||||||
|
post.value = data.posts[0];
|
||||||
|
|
||||||
|
post.value.publish_at = new SMDate(post.value.publish_at, {
|
||||||
|
format: "ymd",
|
||||||
|
utc: true,
|
||||||
|
}).format("yyyy/MM/dd HH:mm:ss");
|
||||||
|
|
||||||
|
applicationStore.setDynamicTitle(post.value.title);
|
||||||
|
|
||||||
|
// Get hero image
|
||||||
|
api.get({
|
||||||
|
url: "/media/{medium}",
|
||||||
|
params: {
|
||||||
|
medium: post.value.hero,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((mediumResult) => {
|
||||||
|
const mediumData = mediumResult.data as MediaResponse;
|
||||||
|
|
||||||
|
if (mediumData && mediumData.medium) {
|
||||||
|
styleObject[
|
||||||
|
"backgroundImage"
|
||||||
|
] = `url('${mediumData.medium.url}')`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get user data
|
||||||
|
api.get({
|
||||||
|
url: "/users/{id}",
|
||||||
|
params: {
|
||||||
|
id: post.value.user_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((userResult) => {
|
||||||
|
const userData = userResult.data as UserResponse;
|
||||||
|
|
||||||
|
if (userData && userData.user) {
|
||||||
|
postUser = userData.user;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* empty */
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pageError.value = 404;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pageError.value = 404;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedPublishAt = (dateStr) => {
|
||||||
|
return new SMDate(dateStr, { format: "yMd" }).format("MMMM d, yyyy");
|
||||||
|
};
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sm-page-post-view {
|
||||||
|
.sm-heading-image {
|
||||||
|
background-color: #eee;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-heading-info {
|
||||||
|
padding: 0 map-get($spacer, 3);
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-author {
|
||||||
|
font-size: 80%;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.sm-page-post-view .sm-heading-image {
|
||||||
|
height: 10rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMContainer class="privacy">
|
<SMContainer class="sm-privacy">
|
||||||
<h1>Privacy Policy</h1>
|
<h1>Privacy Policy</h1>
|
||||||
<h3>We take our customers' privacy & security seriously.</h3>
|
<h3>We take our customers' privacy & security seriously.</h3>
|
||||||
<p>
|
<p>
|
||||||
@@ -322,7 +322,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.privacy {
|
.sm-privacy {
|
||||||
h4 {
|
h4 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,12 +71,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
|
import { useReCaptcha } from "vue-recaptcha-v3";
|
||||||
import SMButton from "../components/SMButton.vue";
|
import SMButton from "../components/SMButton.vue";
|
||||||
import SMDialog from "../components/SMDialog.vue";
|
import SMDialog from "../components/SMDialog.vue";
|
||||||
import SMForm from "../components/SMForm.vue";
|
import SMForm from "../components/SMForm.vue";
|
||||||
import SMFormFooter from "../components/SMFormFooter.vue";
|
import SMFormFooter from "../components/SMFormFooter.vue";
|
||||||
import SMInput from "../components/SMInput.vue";
|
import SMInput from "../components/SMInput.vue";
|
||||||
|
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
import { Form, FormControl } from "../helpers/form";
|
import { Form, FormControl } from "../helpers/form";
|
||||||
import {
|
import {
|
||||||
@@ -89,14 +89,11 @@ import {
|
|||||||
Required,
|
Required,
|
||||||
} from "../helpers/validate";
|
} from "../helpers/validate";
|
||||||
|
|
||||||
import { useReCaptcha } from "vue-recaptcha-v3";
|
|
||||||
|
|
||||||
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
|
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
|
||||||
let abortController: AbortController | null = null;
|
let abortController: AbortController | null = null;
|
||||||
|
|
||||||
const checkUsername = async (value: string): boolean | string => {
|
const checkUsername = (value: string): boolean | string => {
|
||||||
if (lastUsernameCheck.value != value) {
|
if (lastUsernameCheck.value != value) {
|
||||||
console.log("api-get");
|
|
||||||
lastUsernameCheck.value = value;
|
lastUsernameCheck.value = value;
|
||||||
|
|
||||||
if (abortController != null) {
|
if (abortController != null) {
|
||||||
@@ -106,14 +103,13 @@ const checkUsername = async (value: string): boolean | string => {
|
|||||||
|
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
let x = await api
|
api.get({
|
||||||
.get({
|
url: "/users",
|
||||||
url: "/users",
|
params: {
|
||||||
params: {
|
username: value,
|
||||||
username: value,
|
},
|
||||||
},
|
signal: abortController.signal,
|
||||||
signal: abortController.signal,
|
})
|
||||||
})
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log("The username has already been taken.", response);
|
console.log("The username has already been taken.", response);
|
||||||
return "The username has already been taken.";
|
return "The username has already been taken.";
|
||||||
@@ -129,15 +125,13 @@ const checkUsername = async (value: string): boolean | string => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("here");
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formDone = ref(false);
|
const formDone = ref(false);
|
||||||
|
const lastUsernameCheck = ref("");
|
||||||
const form = reactive(
|
const form = reactive(
|
||||||
Form({
|
Form({
|
||||||
first_name: FormControl("", Required()),
|
first_name: FormControl("", Required()),
|
||||||
@@ -159,36 +153,21 @@ const handleSubmit = async () => {
|
|||||||
await api.post({
|
await api.post({
|
||||||
url: "/register",
|
url: "/register",
|
||||||
body: {
|
body: {
|
||||||
first_name: form.first_name.value,
|
first_name: form.controls.first_name.value,
|
||||||
last_name: form.last_name.value,
|
last_name: form.controls.last_name.value,
|
||||||
email: form.email.value,
|
email: form.controls.email.value,
|
||||||
phone: form.phone.value,
|
phone: form.controls.phone.value,
|
||||||
username: form.username.value,
|
username: form.controls.username.value,
|
||||||
password: form.password.value,
|
password: form.controls.password.value,
|
||||||
captcha_token: captcha,
|
captcha_token: captcha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
formDone.value = true;
|
formDone.value = true;
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
form.apiErrors(err);
|
form.apiErrors(error);
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.loading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const lastUsernameCheck = ref("");
|
|
||||||
|
|
||||||
// const debouncedFilter = debounce(checkUsername, 1000);
|
|
||||||
// let oldUsernameValue = "";
|
|
||||||
// watch(
|
|
||||||
// form,
|
|
||||||
// (value) => {
|
|
||||||
// if (value.username.value !== oldUsernameValue) {
|
|
||||||
// oldUsernameValue = value.username.value;
|
|
||||||
// // debouncedFilter(lastUsernameCheck.value);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// { deep: true }
|
|
||||||
// );
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import SMDialog from "../components/SMDialog.vue";
|
|||||||
import SMForm from "../components/SMForm.vue";
|
import SMForm from "../components/SMForm.vue";
|
||||||
import SMFormFooter from "../components/SMFormFooter.vue";
|
import SMFormFooter from "../components/SMFormFooter.vue";
|
||||||
import SMInput from "../components/SMInput.vue";
|
import SMInput from "../components/SMInput.vue";
|
||||||
|
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
import { Form, FormControl } from "../helpers/form";
|
import { Form, FormControl } from "../helpers/form";
|
||||||
import { Required } from "../helpers/validate";
|
import { Required } from "../helpers/validate";
|
||||||
@@ -73,7 +72,7 @@ const handleSubmit = async () => {
|
|||||||
await api.post({
|
await api.post({
|
||||||
url: "/users/resendVerifyEmailCode",
|
url: "/users/resendVerifyEmailCode",
|
||||||
body: {
|
body: {
|
||||||
username: form.username.value,
|
username: form.controls.username.value,
|
||||||
captcha_token: captcha,
|
captcha_token: captcha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -85,8 +84,8 @@ const handleSubmit = async () => {
|
|||||||
} else {
|
} else {
|
||||||
form.apiErrors(error);
|
form.apiErrors(error);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.loading(false);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import SMDialog from "../components/SMDialog.vue";
|
|||||||
import SMForm from "../components/SMForm.vue";
|
import SMForm from "../components/SMForm.vue";
|
||||||
import SMFormFooter from "../components/SMFormFooter.vue";
|
import SMFormFooter from "../components/SMFormFooter.vue";
|
||||||
import SMInput from "../components/SMInput.vue";
|
import SMInput from "../components/SMInput.vue";
|
||||||
|
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
import { Form, FormControl } from "../helpers/form";
|
import { Form, FormControl } from "../helpers/form";
|
||||||
import { And, Max, Min, Password, Required } from "../helpers/validate";
|
import { And, Max, Min, Password, Required } from "../helpers/validate";
|
||||||
@@ -65,7 +64,12 @@ const form = reactive(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (useRoute().query.code !== undefined) {
|
if (useRoute().query.code !== undefined) {
|
||||||
form.code.value = useRoute().query.code;
|
let queryCode = useRoute().query.code;
|
||||||
|
if (Array.isArray(queryCode)) {
|
||||||
|
queryCode = queryCode[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
form.controls.code.value = queryCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
@@ -78,17 +82,17 @@ const handleSubmit = async () => {
|
|||||||
await api.post({
|
await api.post({
|
||||||
url: "/users/resetPassword",
|
url: "/users/resetPassword",
|
||||||
body: {
|
body: {
|
||||||
code: form.code.value,
|
code: form.controls.code.value,
|
||||||
password: form.password.value,
|
password: form.controls.password.value,
|
||||||
captcha_token: captcha,
|
captcha_token: captcha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
formDone.value = true;
|
formDone.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
form.apiError(error);
|
form.apiErrors(error);
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.loading(false);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage class="rules">
|
<SMPage class="sm-rules">
|
||||||
<h1>Rules</h1>
|
<h1>Rules</h1>
|
||||||
<p>
|
<p>
|
||||||
Oh gosh, no body likes rules but to ensure that we have a fun,
|
Oh gosh, no body likes rules but to ensure that we have a fun,
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.rules {
|
.sm-rules {
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage class="terms">
|
<SMPage class="sm-terms">
|
||||||
<h1>Terms and Conditions</h1>
|
<h1>Terms and Conditions</h1>
|
||||||
<p>
|
<p>
|
||||||
Please read these terms carefully. By accessing or using our website
|
Please read these terms carefully. By accessing or using our website
|
||||||
@@ -562,5 +562,3 @@
|
|||||||
</p>
|
</p>
|
||||||
</SMPage>
|
</SMPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const handleSubmit = async () => {
|
|||||||
await api.delete({
|
await api.delete({
|
||||||
url: "/subscriptions",
|
url: "/subscriptions",
|
||||||
body: {
|
body: {
|
||||||
email: form.email.value,
|
email: form.controls.email.value,
|
||||||
captcha_token: captcha,
|
captcha_token: captcha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -73,13 +73,18 @@ const handleSubmit = async () => {
|
|||||||
formDone.value = true;
|
formDone.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
form.apiErrors(error);
|
form.apiErrors(error);
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
form.loading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (useRoute().query.email !== undefined) {
|
if (useRoute().query.email !== undefined) {
|
||||||
form.email.value = useRoute().query.email;
|
let queryEmail = useRoute().query.email;
|
||||||
|
if (Array.isArray(queryEmail)) {
|
||||||
|
queryEmail = queryEmail[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
form.controls.email.value = queryEmail;
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,42 +9,42 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/posts')"
|
v-if="userStore.permissions.includes('admin/posts')"
|
||||||
to="/dashboard/posts"
|
:to="{ name: 'dashboard-post-list' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="newspaper-outline" />
|
<ion-icon name="newspaper-outline" />
|
||||||
<h2>Posts</h2>
|
<h2>Posts</h2>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/users')"
|
v-if="userStore.permissions.includes('admin/users')"
|
||||||
:to="{ name: 'user-list' }"
|
:to="{ name: 'dashboard-user-list' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="people-outline" />
|
<ion-icon name="people-outline" />
|
||||||
<h2>Users</h2>
|
<h2>Users</h2>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/events')"
|
v-if="userStore.permissions.includes('admin/events')"
|
||||||
to="/dashboard/events"
|
:to="{ name: 'dashboard-event-list' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="calendar-outline" />
|
<ion-icon name="calendar-outline" />
|
||||||
<h2>Events</h2>
|
<h2>Events</h2>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/courses')"
|
v-if="userStore.permissions.includes('admin/courses')"
|
||||||
to="/dashboard/courses"
|
:to="{ name: 'dashboard-course-list' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="school-outline" />
|
<ion-icon name="school-outline" />
|
||||||
<h2>{{ courseBoxTitle }}</h2>
|
<h2>{{ courseBoxTitle }}</h2>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/media')"
|
v-if="userStore.permissions.includes('admin/media')"
|
||||||
to="/dashboard/media"
|
:to="{ name: 'dashboard-media-list' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="film-outline" />
|
<ion-icon name="film-outline" />
|
||||||
<h2>Media</h2>
|
<h2>Media</h2>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('logs/discord')"
|
v-if="userStore.permissions.includes('logs/discord')"
|
||||||
:to="{ name: 'discord-bot-logs' }"
|
:to="{ name: 'dashboard-discord-bot-logs' }"
|
||||||
class="box">
|
class="box">
|
||||||
<ion-icon name="logo-discord" />
|
<ion-icon name="logo-discord" />
|
||||||
<h2>Discord Bot Logs</h2>
|
<h2>Discord Bot Logs</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user