lots o updates
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
DB::table('users')->whereNull('phone')->update(['phone' => '']);
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('phone')->default("")->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('phone')->nullable(true)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('display_name')->default("");
|
||||
});
|
||||
|
||||
// Update existing rows with display_name
|
||||
DB::table('users')->select('id', 'username')->orderBy('id')->chunk(100, function ($users) {
|
||||
foreach ($users as $user) {
|
||||
DB::table('users')->where('id', $user->id)->update(['display_name' => $user->username]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('display_name');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -79,6 +79,13 @@ a:visited {
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
li,
|
||||
.html {
|
||||
text-rendering: optimizeLegibility;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
@@ -91,30 +98,6 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-family: var(--header-font-family);
|
||||
font-weight: 800;
|
||||
padding: 16px 32px 16px 32px;
|
||||
border: 0;
|
||||
background-color: var(--base-color-light);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
color: var(--link-color);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(85%);
|
||||
}
|
||||
}
|
||||
|
||||
// Who knows why ion-icon randomally sets this to hidden.....
|
||||
// ion-icon {
|
||||
// visibility: visible;
|
||||
@@ -190,23 +173,23 @@ li {
|
||||
// }
|
||||
// }
|
||||
|
||||
// /* SM Dialog */
|
||||
// .sm-dialog-outer {
|
||||
// position: fixed;
|
||||
// display: flex;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// bottom: 0;
|
||||
// right: 0;
|
||||
// flex-direction: column;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// z-index: 1000;
|
||||
// padding: 1rem;
|
||||
// }
|
||||
/* SM Dialog */
|
||||
.dialog-outer {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
// .sm-dialog-outer:last-of-type {
|
||||
// background-color: rgba(0, 0, 0, 0.4);
|
||||
// backdrop-filter: blur(2px);
|
||||
// -webkit-backdrop-filter: blur(2px);
|
||||
// }
|
||||
.dialog-outer:last-of-type {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(2px);
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
:root {
|
||||
// yes
|
||||
--default-font-size: 18px;
|
||||
--default-font-family: "Nunito", "Nunito override", "Arial", "Helvetica",
|
||||
sans-serif;
|
||||
--default-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
// --default-font-family: "Nunito", "Nunito override", "Arial", "Helvetica",
|
||||
// sans-serif;
|
||||
|
||||
// yes
|
||||
--base-color: #eee;
|
||||
--base-color-text: rgba(0, 0, 0, 0.8);
|
||||
--base-color-text: #456;
|
||||
--base-color-border: #999;
|
||||
--base-color-light: #fff;
|
||||
|
||||
@@ -25,6 +27,7 @@
|
||||
|
||||
// yes
|
||||
--primary-color: #35a5f1;
|
||||
--primary-color-hover: #f1fdff;
|
||||
|
||||
--primary-color-dark: #0e80ce;
|
||||
--primary-color-darker: #095589;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<router-link :to="props.to" :class="['sm-article-card', `${props.type}`]">
|
||||
<div
|
||||
class="thumbnail"
|
||||
:style="[{ backgroundImage: `url('${props.image}')` }]"></div>
|
||||
<div class="content">
|
||||
<h3>{{ props.title }}</h3>
|
||||
<p class="excerpt">{{ props.excerpt }}</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
to: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: false,
|
||||
},
|
||||
excerpt: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-article-card {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
.thumbnail {
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 7px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
&.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -184,7 +184,7 @@ const handleClickItem = (item: string) => {
|
||||
display: inline-block;
|
||||
font-family: var(--header-font-family);
|
||||
font-weight: 800;
|
||||
padding: 16px 32px 16px 32px;
|
||||
padding: 12px 32px 12px 32px;
|
||||
border: 0;
|
||||
background-color: var(--base-color-light);
|
||||
text-decoration: none;
|
||||
@@ -195,7 +195,11 @@ const handleClickItem = (item: string) => {
|
||||
user-select: none;
|
||||
|
||||
.button-label {
|
||||
display: inline-block;
|
||||
padding: 2px 0 3px 0;
|
||||
|
||||
ion-icon {
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: -8px 0;
|
||||
@@ -240,7 +244,7 @@ const handleClickItem = (item: string) => {
|
||||
background-color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 255, 0.5);
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
:class="['sm-column', { 'flex-fill': fill && width == '' }]"
|
||||
:class="['column', { 'flex-fill': fill && width == '' }]"
|
||||
:style="styles">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@@ -28,13 +28,13 @@ if (props.width != "") {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-column {
|
||||
.column {
|
||||
display: flex;
|
||||
margin: map-get($spacer, 2);
|
||||
margin: 0 12px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sm-row .sm-row .sm-column {
|
||||
.row .row .column {
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@@ -40,9 +40,10 @@ const slots = useSlots();
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding: 0 16px 0 16px;
|
||||
padding: 0 16px;
|
||||
max-width: 1200px;
|
||||
align-items: center;
|
||||
// align-items: center;
|
||||
margin: 0 auto;
|
||||
|
||||
&.full {
|
||||
padding: 0;
|
||||
|
||||
@@ -19,8 +19,8 @@ const dialogRefs = shallowReactive<DialogInstance[]>([]);
|
||||
export default defineComponent({
|
||||
name: "SMDialogList",
|
||||
template: `
|
||||
<div class="sm-dialog-list">
|
||||
<div v-for="(dialogRef, index) in dialogRefList" :key="index" class="sm-dialog-outer">
|
||||
<div class="dialog-list">
|
||||
<div v-for="(dialogRef, index) in dialogRefList" :key="index" class="dialog-outer">
|
||||
<component
|
||||
:is="dialogRef.dialog"
|
||||
v-if="dialogRef && dialogRef.wrapper === name"
|
||||
|
||||
@@ -90,7 +90,12 @@
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'terms' }"
|
||||
<router-link :to="{ name: 'code-of-conduct' }"
|
||||
>Code of Conduct</router-link
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'terms-and-conditions' }"
|
||||
>Terms & Conditions</router-link
|
||||
>
|
||||
</li>
|
||||
@@ -99,9 +104,6 @@
|
||||
>Privacy Policy</router-link
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'rules' }">Rules</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
|
||||
@@ -65,7 +65,7 @@ const computedContent = computed(() => {
|
||||
);
|
||||
|
||||
return {
|
||||
template: `<div class="sm-content">${html}</div>`,
|
||||
template: `<div class="html">${html}</div>`,
|
||||
components: {
|
||||
SMImageGallery,
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
name="alert-circle-outline"></ion-icon>
|
||||
<ion-icon
|
||||
v-if="
|
||||
props.showClear && value.length > 0 && !feedbackInvalid
|
||||
props.showClear && value?.length > 0 && !feedbackInvalid
|
||||
"
|
||||
class="clear-icon"
|
||||
name="close-outline"
|
||||
@@ -96,6 +96,11 @@ const props = defineProps({
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
feedbackInvalid: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
@@ -132,14 +137,22 @@ const id = ref(
|
||||
? props.control
|
||||
: ""
|
||||
);
|
||||
const feedbackInvalid = ref("");
|
||||
const active = ref(value.value.length > 0);
|
||||
const feedbackInvalid = ref(props.feedbackInvalid);
|
||||
const active = ref(value.value?.length ?? 0 > 0);
|
||||
const focused = ref(false);
|
||||
const disabled = ref(props.disabled);
|
||||
|
||||
watch(
|
||||
() => value.value,
|
||||
(newValue) => {
|
||||
active.value = newValue.length > 0;
|
||||
active.value = newValue.length > 0 || focused.value == true;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.feedbackInvalid,
|
||||
(newValue) => {
|
||||
feedbackInvalid.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -174,10 +187,13 @@ if (form) {
|
||||
|
||||
const handleFocus = () => {
|
||||
active.value = true;
|
||||
focused.value = true;
|
||||
};
|
||||
|
||||
const handleBlur = async () => {
|
||||
active.value = value.value != "";
|
||||
active.value = value.value?.length ?? 0 > 0;
|
||||
focused.value = false;
|
||||
emits("change");
|
||||
|
||||
if (control) {
|
||||
await control.validate();
|
||||
@@ -295,7 +311,7 @@ const handleClear = () => {
|
||||
background-color: #ccc;
|
||||
border-radius: 50%;
|
||||
font-size: 80%;
|
||||
padding: 1px;
|
||||
padding: 1px 1px 1px 0px;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
@@ -310,13 +326,18 @@ const handleClear = () => {
|
||||
border-radius: 8px;
|
||||
background-color: var(--base-color-light);
|
||||
color: var(--base-color-text);
|
||||
|
||||
&:disabled {
|
||||
background-color: hsl(0, 0%, 92%);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-help {
|
||||
display: block;
|
||||
font-size: 85%;
|
||||
font-size: 70%;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.input-invalid {
|
||||
|
||||
@@ -51,10 +51,8 @@ const tabGroups = [
|
||||
[
|
||||
{ title: "Contact", to: "/contact" },
|
||||
{ title: "Code of Conduct", to: "/code-of-conduct" },
|
||||
{ title: "Privacy", to: "/page" },
|
||||
{ title: "Governance", to: "/page" },
|
||||
{ title: "Teams", to: "/login" },
|
||||
{ title: "License", to: "/page" },
|
||||
{ title: "Terms and Conditions", to: "/terms-and-conditions" },
|
||||
{ title: "Privacy", to: "/privacy" },
|
||||
],
|
||||
];
|
||||
|
||||
@@ -115,16 +113,19 @@ const tabs = () => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-family: var(--header-font-family);
|
||||
font-weight: 800;
|
||||
font-size: 18px;
|
||||
text-decoration: none;
|
||||
padding: 16px 24px;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
&:hover:not(.active) {
|
||||
color: rgba(255, 255, 255);
|
||||
background-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
@@ -132,11 +133,24 @@ const tabs = () => {
|
||||
&.active {
|
||||
background-color: var(--base-color);
|
||||
color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.masthead .tabs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.masthead {
|
||||
background-color: var(--primary-color-light);
|
||||
|
||||
@@ -266,20 +266,24 @@ onUnmounted(() => {
|
||||
margin: 0;
|
||||
padding: 0 0 12px 0;
|
||||
|
||||
li a {
|
||||
color: var(--base-color-text);
|
||||
display: block;
|
||||
padding: 12px 0;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
li {
|
||||
margin-bottom: 0;
|
||||
|
||||
&:hover {
|
||||
a {
|
||||
color: var(--base-color-text);
|
||||
display: block;
|
||||
padding: 12px 0;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
background-color: hsla(0, 0%, 50%, 0.1);
|
||||
}
|
||||
|
||||
span {
|
||||
padding-left: 12px;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: hsla(0, 0%, 50%, 0.1);
|
||||
}
|
||||
|
||||
span {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
resources/js/components/SMNoItems.vue
Normal file
32
resources/js/components/SMNoItems.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="no-items">
|
||||
<ion-icon name="alert-circle-outline" />
|
||||
<p>{{ props.text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: "No items found",
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.no-items {
|
||||
margin-top: 32px;
|
||||
text-align: center;
|
||||
|
||||
ion-icon {
|
||||
font-size: 400%;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 130%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -128,16 +128,15 @@ const handleClickPage = (page: number): void => {
|
||||
font-family: var(--header-font-family);
|
||||
font-size: 90%;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
margin: 24px auto;
|
||||
box-shadow: var(--base-shadow);
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
background-color: var(--base-color-light);
|
||||
padding: 12px 16px;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: var(--base-shadow);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
&.active {
|
||||
background-color: var(--primary-color);
|
||||
@@ -159,8 +158,8 @@ const handleClickPage = (page: number): void => {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(115%);
|
||||
&:hover:not(.active) {
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'sm-row',
|
||||
{ 'row-break-lg': breakLarge },
|
||||
{ 'flex-fill': fill },
|
||||
]">
|
||||
:class="['row', { 'row-break-lg': breakLarge }, { 'flex-fill': fill }]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,7 +21,7 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-row {
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0 auto;
|
||||
@@ -35,7 +31,7 @@ defineProps({
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.sm-row.row-break-lg {
|
||||
.row.row-break-lg {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ const handleRowClick = (item) => {
|
||||
tr {
|
||||
&:hover {
|
||||
td {
|
||||
background-color: rgba(0, 0, 255, 0.1) !important;
|
||||
background-color: var(--primary-color-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,15 @@
|
||||
<template>
|
||||
<div class="sm-toolbar">
|
||||
<div v-if="slots.left" class="sm-toolbar-column sm-toolbar-column-left">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from "vue";
|
||||
|
||||
const slots = useSlots();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-toolbar {
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: map-get($spacer, 2);
|
||||
width: 100%;
|
||||
|
||||
.sm-toolbar-column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
|
||||
&.sm-toolbar-column-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin: 0 map-get($spacer, 1);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.sm-toolbar-column-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.sm-toolbar {
|
||||
.sm-toolbar-column {
|
||||
flex-direction: column;
|
||||
|
||||
& > * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -136,7 +136,7 @@ export class SMDate {
|
||||
parsedMonth: number = 0,
|
||||
parsedYear: number = 0;
|
||||
|
||||
if (year.length == 3 || year.length >= 5) {
|
||||
if (year == undefined || year.length == 3 || year.length >= 5) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,14 +101,6 @@ export const routes = [
|
||||
},
|
||||
component: () => import("@/views/Unsubscribe.vue"),
|
||||
},
|
||||
{
|
||||
path: "/terms",
|
||||
name: "terms",
|
||||
meta: {
|
||||
title: "Terms and Conditions",
|
||||
},
|
||||
component: () => import("@/views/Terms.vue"),
|
||||
},
|
||||
{
|
||||
path: "/minecraft",
|
||||
name: "minecraft",
|
||||
@@ -142,6 +134,10 @@ export const routes = [
|
||||
},
|
||||
component: () => import("@/views/Contact.vue"),
|
||||
},
|
||||
{
|
||||
path: "/conduct",
|
||||
redirect: { name: "code-of-conduct" },
|
||||
},
|
||||
{
|
||||
path: "/code-of-conduct",
|
||||
name: "code-of-conduct",
|
||||
@@ -150,6 +146,18 @@ export const routes = [
|
||||
},
|
||||
component: () => import("@/views/CodeOfConduct.vue"),
|
||||
},
|
||||
{
|
||||
path: "/terms",
|
||||
redirect: { name: "terms-and-conditions" },
|
||||
},
|
||||
{
|
||||
path: "/terms-and-conditions",
|
||||
name: "terms-and-conditions",
|
||||
meta: {
|
||||
title: "Terms and Conditions",
|
||||
},
|
||||
component: () => import("@/views/TermsAndConditions.vue"),
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
name: "register",
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
<template>
|
||||
<SMPage class="sm-page-post-view" :page-error="pageError">
|
||||
<SMContainer>
|
||||
<div class="sm-post-hero" :style="backgroundStyle"></div>
|
||||
<div class="sm-heading-info">
|
||||
<h1>{{ post.title }}</h1>
|
||||
<div class="sm-date-author small">
|
||||
<ion-icon name="calendar-outline" />
|
||||
{{ formattedPublishAt(post.publish_at) }}, by
|
||||
{{ post.user.username }}
|
||||
</div>
|
||||
</div>
|
||||
<SMHTML :html="post.content" />
|
||||
<SMAttachments :attachments="post.attachments || []" />
|
||||
</SMContainer>
|
||||
</SMPage>
|
||||
<div
|
||||
class="thumbnail"
|
||||
:style="{ backgroundImage: `url('${backgroundImageUrl}')` }"></div>
|
||||
<SMContainer narrow>
|
||||
<h1 class="title">{{ post.title }}</h1>
|
||||
<div class="author">By {{ post.user.username }}</div>
|
||||
<div class="date">{{ formattedDate(post.publish_at) }}</div>
|
||||
<SMHTML :html="post.content" class="content" />
|
||||
<SMAttachments :attachments="post.attachments || []" />
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -52,7 +47,10 @@ let pageLoading = ref(false);
|
||||
*/
|
||||
let postUser: User | null = null;
|
||||
|
||||
let backgroundStyle = {};
|
||||
/**
|
||||
* Thumbnail image URL.
|
||||
*/
|
||||
let backgroundImageUrl = ref("");
|
||||
|
||||
/**
|
||||
* Load the page data.
|
||||
@@ -81,12 +79,7 @@ const handleLoad = async () => {
|
||||
utc: true,
|
||||
}).format("yyyy/MM/dd HH:mm:ss");
|
||||
|
||||
backgroundStyle = {
|
||||
backgroundImage: `url('${mediaGetVariantUrl(
|
||||
post.value.hero
|
||||
)}')`,
|
||||
};
|
||||
|
||||
backgroundImageUrl.value = mediaGetVariantUrl(post.value.hero);
|
||||
applicationStore.setDynamicTitle(post.value.title);
|
||||
} else {
|
||||
pageError.value = 404;
|
||||
@@ -101,7 +94,13 @@ const handleLoad = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formattedPublishAt = (dateStr) => {
|
||||
/**
|
||||
* Format Date
|
||||
*
|
||||
* @param dateStr Date string.
|
||||
* @returns Formatted date.
|
||||
*/
|
||||
const formattedDate = (dateStr) => {
|
||||
return new SMDate(dateStr, { format: "yMd" }).format("MMMM d, yyyy");
|
||||
};
|
||||
|
||||
@@ -109,46 +108,34 @@ handleLoad();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-page-post-view {
|
||||
.sm-container {
|
||||
width: 70%;
|
||||
padding: 64px 0;
|
||||
}
|
||||
|
||||
.sm-post-hero {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
border-radius: 6px;
|
||||
.page-article {
|
||||
.thumbnail {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
aspect-ratio: 16 / 9;
|
||||
max-height: 640px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sm-heading-info {
|
||||
padding: 0 map-get($spacer, 3);
|
||||
margin-bottom: map-get($spacer, 4);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
margin-top: 64px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sm-content {
|
||||
padding: 0 map-get($spacer, 3);
|
||||
line-height: 1.5rem;
|
||||
.author {
|
||||
margin-top: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-top: 16px;
|
||||
font-weight: 700;
|
||||
filter: brightness(175%);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<SMMastHead title="Blog" />
|
||||
<SMContainer>
|
||||
<SMInput type="text" label="Search articles" v-model="searchInput">
|
||||
<SMInput
|
||||
type="text"
|
||||
label="Search articles"
|
||||
v-model="searchInput"
|
||||
show-clear>
|
||||
<template #append
|
||||
><SMButton
|
||||
type="primary"
|
||||
@@ -11,11 +15,13 @@
|
||||
/></template>
|
||||
</SMInput>
|
||||
<SMPagination
|
||||
v-if="postsTotal > postsPerPage"
|
||||
v-model="postsPage"
|
||||
:total="postsTotal"
|
||||
:per-page="postsPerPage" />
|
||||
<div class="posts">
|
||||
<article
|
||||
<router-link
|
||||
:to="{ name: 'article', params: { slug: post.slug } }"
|
||||
class="article-card"
|
||||
v-for="(post, idx) in posts"
|
||||
:key="idx">
|
||||
@@ -27,28 +33,30 @@
|
||||
'medium'
|
||||
)})`,
|
||||
}"></div>
|
||||
<div class="content">
|
||||
{{ post.content }}
|
||||
<div class="info">
|
||||
{{ post.user.username }} -
|
||||
{{ computedDate(post.publish_at) }}
|
||||
</div>
|
||||
</article>
|
||||
<h3 class="title">{{ post.title }}</h3>
|
||||
<p class="content">
|
||||
{{ excerpt(post.content) }}
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref, watch } from "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 { api } from "../helpers/api";
|
||||
import { Post, PostCollection } from "../helpers/api.types";
|
||||
import { SMDate } from "../helpers/datetime";
|
||||
import { mediaGetVariantUrl } from "../helpers/media";
|
||||
import SMMastHead from "../components/SMMastHead.vue";
|
||||
import SMInput from "../components/SMInput.vue";
|
||||
import SMForm from "../components/SMForm.vue";
|
||||
import SMButton from "../components/SMButton.vue";
|
||||
import { excerpt } from "../helpers/string";
|
||||
|
||||
const message = ref("");
|
||||
const pageLoading = ref(true);
|
||||
@@ -102,6 +110,10 @@ const handleLoad = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const computedDate = (date) => {
|
||||
return new SMDate(date, { format: "yMd" }).format("d MMMM yyyy");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => postsPage.value,
|
||||
() => {
|
||||
@@ -113,31 +125,58 @@ handleLoad();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.posts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
.page-blog {
|
||||
.posts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
|
||||
.article-card {
|
||||
.thumbnail {
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 7px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 5px 10px -3px #00000078;
|
||||
.article-card {
|
||||
text-decoration: none;
|
||||
color: var(--base-color-text);
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
|
||||
.thumbnail {
|
||||
filter: brightness(115%);
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 7px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: var(--base-shadow);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 16px 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.posts {
|
||||
.page-blog .posts {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.posts {
|
||||
.page-blog .posts {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SMMastHead title="Code of Conduct" />
|
||||
<SMContainer>
|
||||
<div class="container-text">
|
||||
<SMContainer narrow>
|
||||
<template #inner>
|
||||
<p>
|
||||
STEMMechanics supports the international community open to
|
||||
everyone without discrimination. We want this community to be a
|
||||
@@ -12,11 +12,24 @@
|
||||
</p>
|
||||
<h3>Philosophy</h3>
|
||||
<p>
|
||||
If you have a question or would like help with a project, you
|
||||
can send it our way using the form on this page or be emailing
|
||||
<a href="mailto:hello@stemmechanics.com.au"
|
||||
>hello@stemmechanics.com.au</a
|
||||
>.
|
||||
In the STEMMechanics community, participants from all over the
|
||||
world come together to create and work on STEM projects. This is
|
||||
made possible by the support, hard work, and enthusiasm of
|
||||
people who collaborate towards the common goal of creating great
|
||||
ideas. Cooperation at such a scale requires common guidelines to
|
||||
ensure a positive and inspiring atmosphere in the community.
|
||||
</p>
|
||||
<p>
|
||||
This is why we have this Code of Conduct: it explains the type
|
||||
of community we want to have. The rules below are not applied to
|
||||
all interactions with a simple matching algorithm. Human
|
||||
interactions happen in context and are complex. Perceived
|
||||
violations are evaluated by real humans who will try to
|
||||
interpret the interactions and the rules with kindness.
|
||||
Accordingly, there is no need to hypothesize on how these rules
|
||||
would affect normal interactions. Be reasonable, the
|
||||
<a href="#coc-team">Code of Conduct team</a> surely will be as
|
||||
well.
|
||||
</p>
|
||||
<h3>Application</h3>
|
||||
<p>
|
||||
@@ -47,18 +60,84 @@
|
||||
please accept it gracefully.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Code of Conduct team</h3>
|
||||
<h3>Restricted conduct</h3>
|
||||
<p>
|
||||
Participating in restricted conduct will lead to a warning from
|
||||
community moderators and/or the Code of Conduct team and may
|
||||
lead to exclusion from the community in the form of a ban from
|
||||
one or all platforms.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
STEMMechanics is committed to providing a friendly and safe
|
||||
environment for everyone, regardless of level of experience,
|
||||
gender identity and expression, sexual orientation,
|
||||
disability, physical appearance, body size, race, ethnicity,
|
||||
language proficiency, age, political orientation,
|
||||
nationality, religion, or other similar characteristics. We
|
||||
do not tolerate harassment or discrimination of participants
|
||||
in any form.
|
||||
</li>
|
||||
<li>
|
||||
In particular, we strive to be welcoming to all and to
|
||||
ensure that anyone can take a more active role in the
|
||||
community and a project. Targeted harassment of minorities
|
||||
or individuals is unacceptable.
|
||||
</li>
|
||||
<li>Aggressive or offensive behavior is not acceptable.</li>
|
||||
<li>
|
||||
You will be excluded from participating in the community if
|
||||
you insult, demean, harass, intentionally make others
|
||||
uncomfortable by any means, or participate in any other
|
||||
hateful conduct, either publicly or privately.
|
||||
</li>
|
||||
<li>
|
||||
Likewise, any spamming, trolling, flaming, baiting, or other
|
||||
attention-stealing behavior is not welcome and will result
|
||||
in exclusion from the community.
|
||||
</li>
|
||||
<li>
|
||||
Any form of retaliation against a participant who contacts
|
||||
the Code of Conduct team is completely unacceptable,
|
||||
regardless of the outcome of the complaint. Any such
|
||||
behavior will result in exclusion from the community.
|
||||
</li>
|
||||
<li>
|
||||
For certainty, any conduct which could reasonably be
|
||||
considered inappropriate in a professional setting is not
|
||||
acceptable.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Reporting a breach</h3>
|
||||
<p>
|
||||
If you witness or are involved in an interaction with another
|
||||
community member that you think may violate this Code of
|
||||
Conduct, please contact STEMMechanics
|
||||
<a href="#coc-team">Code of Conduct team</a>.
|
||||
</p>
|
||||
<p>
|
||||
STEMMechanics recognizes that it can be difficult to come
|
||||
forward in cases of a violation of the Code of Conduct. To make
|
||||
it easier to report violations, we provide a single point of
|
||||
contact via email at:
|
||||
<a href="conduct@stemmechanics.com.au"
|
||||
>conduct@stemmechanics.com.au</a
|
||||
>. If you are more comfortable reaching out to a single person,
|
||||
you are also welcome to contact one or more members of the team
|
||||
using their personal emails listed below, or via direct
|
||||
messaging on community platforms where they are present.
|
||||
</p>
|
||||
<h3 id="coc-item">Code of Conduct team</h3>
|
||||
<ul>
|
||||
<li>James Collins, james@stemmechanics.com.au</li>
|
||||
<ul>
|
||||
<li>
|
||||
GitHub / Discord / Reddit:
|
||||
GitHub / Discord / Reddit / Twitter:
|
||||
<span class="italic">nomadjimbob</span>
|
||||
</li>
|
||||
<li>Languages: English</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
@@ -67,29 +146,9 @@ import SMMastHead from "../components/SMMastHead.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container-text {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.4em;
|
||||
|
||||
.page-code-of-conduct {
|
||||
h3 {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.sm-contact-socials {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
font-size: 200%;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
margin: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SMContainer class="sm-privacy">
|
||||
<template #container>
|
||||
<h1>Privacy Policy</h1>
|
||||
<SMMastHead title="Privacy Policy" />
|
||||
<SMContainer narrow>
|
||||
<template #inner>
|
||||
<h3>We take our customers' privacy & security seriously.</h3>
|
||||
<p>
|
||||
At STEMMechanics, we take our customers' privacy and security
|
||||
@@ -23,8 +23,8 @@
|
||||
<p>
|
||||
By using the Website and our online services, you agree to
|
||||
accept the Privacy Policy and the Site's Terms and Conditions
|
||||
<router-link :to="{ name: 'terms' }"
|
||||
>https://www.stemmechanics.com.au/terms</router-link
|
||||
<router-link :to="{ name: 'terms-and-conditions' }"
|
||||
>https://www.stemmechanics.com.au/terms-and-conditions</router-link
|
||||
>
|
||||
(Terms and Conditions). Where the Privacy Policy uses a word
|
||||
starting with a capital letter, that term will be defined in the
|
||||
@@ -336,14 +336,18 @@
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SMMastHead from "../components/SMMastHead.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sm-privacy {
|
||||
h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
.page-privacy {
|
||||
h3 {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SMPage class="sm-page-terms">
|
||||
<template #container>
|
||||
<h1>Terms and Conditions</h1>
|
||||
<SMMastHead title="Terms and Conditions" />
|
||||
<SMContainer narrow>
|
||||
<template #inner>
|
||||
<p>
|
||||
Please read these terms carefully. By accessing or using our
|
||||
website and online servers, you agree to be bound by these terms
|
||||
@@ -20,7 +20,7 @@
|
||||
agrees to indemnify you and STEMMechanics for its violations of
|
||||
these Terms.
|
||||
</p>
|
||||
<h4>1. Eligibility, registration & account</h4>
|
||||
<h3>1. Eligibility, registration & account</h3>
|
||||
<p>
|
||||
You must be 18 years of age to use the Website. If you are under
|
||||
18 years of age you must have the permission of your parent or
|
||||
@@ -50,7 +50,7 @@
|
||||
if you discover or otherwise suspect any security breaches
|
||||
related to the Sites.
|
||||
</p>
|
||||
<h4>2. Ownership of site content</h4>
|
||||
<h3>2. Ownership of site content</h3>
|
||||
<p>
|
||||
Unless otherwise indicated on our Sites, the Sites and all
|
||||
content and materials therein, including but not limited to the
|
||||
@@ -86,7 +86,7 @@
|
||||
intellectual property rights, whether by estoppel, implication
|
||||
or otherwise. This license is revocable at any time.
|
||||
</p>
|
||||
<h4>3. Hyperlinks</h4>
|
||||
<h3>3. Hyperlinks</h3>
|
||||
<p>
|
||||
You are granted a limited, non-exclusive right to create a text
|
||||
hyperlink to the Sites for non-commercial purposes, provided
|
||||
@@ -119,7 +119,7 @@
|
||||
including privacy and data gathering practices, of any site to
|
||||
which you navigate from the Sites.
|
||||
</p>
|
||||
<h4>4. User content</h4>
|
||||
<h3>4. User content</h3>
|
||||
<p>
|
||||
The Sites may include discussion blogs, profiles, product
|
||||
reviews or other interactive features or areas (collectively,
|
||||
@@ -223,7 +223,7 @@
|
||||
among other things, termination or suspension of your rights to
|
||||
use the Sites.
|
||||
</p>
|
||||
<h4>5. Rights in user content</h4>
|
||||
<h3>5. Rights in user content</h3>
|
||||
<p>
|
||||
Except as otherwise provided herein, on the Sites or in a
|
||||
separate agreement with us (such as the rules of a STEMMechanics
|
||||
@@ -261,7 +261,7 @@
|
||||
guidelines or policies or any applicable law, rule or
|
||||
regulation.
|
||||
</p>
|
||||
<h4>6. Feedback</h4>
|
||||
<h3>6. Feedback</h3>
|
||||
<p>
|
||||
Separate and apart from User Content, you have the ability to
|
||||
submit questions, comments suggestions, reviews, ideas, plans,
|
||||
@@ -279,7 +279,7 @@
|
||||
idea might be great, but we may have already had the same or a
|
||||
similar idea and we do not want disputes.
|
||||
</p>
|
||||
<h4>7. User conduct</h4>
|
||||
<h3>7. User conduct</h3>
|
||||
<p>
|
||||
You agree that you will not violate any law, contract or
|
||||
intellectual property or other third party right or commit a
|
||||
@@ -364,14 +364,14 @@
|
||||
before contacting or meeting anyone (online or offline) that is
|
||||
unfamiliar to you.
|
||||
</p>
|
||||
<h4>8. No third-party beneficiaries</h4>
|
||||
<h3>8. No third-party beneficiaries</h3>
|
||||
<p>
|
||||
These Terms are for the benefit of, and will be enforceable by,
|
||||
the parties only. These Terms are not intended to confer any
|
||||
right or benefit on any third party or to create any obligations
|
||||
or liability of a party to any such third party.
|
||||
</p>
|
||||
<h4>9. Indemnification</h4>
|
||||
<h3>9. Indemnification</h3>
|
||||
<p>
|
||||
To the fullest extent permitted by applicable law, you agree to
|
||||
defend, indemnify and hold harmless STEMMechanics and our
|
||||
@@ -385,7 +385,7 @@
|
||||
provide; (d) your violation of these Terms; and (e) your
|
||||
violation of any rights of another.
|
||||
</p>
|
||||
<h4>10. Disclaimers</h4>
|
||||
<h3>10. Disclaimers</h3>
|
||||
<p>
|
||||
Except as expressly provided, the Sites, Site Content, User
|
||||
Content and services provided on or in connection with the Sites
|
||||
@@ -413,7 +413,7 @@
|
||||
is not a substitute for in-person guidance by a qualified
|
||||
instructor.
|
||||
</p>
|
||||
<h4>11. Liability</h4>
|
||||
<h3>11. Liability</h3>
|
||||
<p>
|
||||
To the fullest extent permitted by applicable law, in no event
|
||||
shall the STEMMechanics parties be liable for any special,
|
||||
@@ -433,13 +433,13 @@
|
||||
access to an STEMMechanics party's records, programs or
|
||||
services.
|
||||
</p>
|
||||
<h4>12. Modifications to site</h4>
|
||||
<h3>12. Modifications to site</h3>
|
||||
<p>
|
||||
STEMMechanics reserves the right to modify or discontinue,
|
||||
temporarily or permanently, the Sites or any features or
|
||||
portions thereof without prior notice.
|
||||
</p>
|
||||
<h4>13. Termination</h4>
|
||||
<h3>13. Termination</h3>
|
||||
<p>
|
||||
You may terminate the Terms at any time by closing your account,
|
||||
discontinuing your use of the Sites and providing STEMMechanics
|
||||
@@ -449,14 +449,14 @@
|
||||
block or prevent your future access to and use of the Sites or
|
||||
any portion of the Sites.
|
||||
</p>
|
||||
<h4>14. Severability</h4>
|
||||
<h3>14. Severability</h3>
|
||||
<p>
|
||||
If any provision of these Terms shall be deemed unlawful, void
|
||||
or for any reason unenforceable, then that provision shall be
|
||||
deemed severable from these Terms and shall not affect the
|
||||
validity and enforceability of any remaining provisions.
|
||||
</p>
|
||||
<h4>15. Ordering online</h4>
|
||||
<h3>15. Ordering online</h3>
|
||||
<p>
|
||||
Upon completing your order and submitting it through the
|
||||
checkout system, an order reference number will be issued to you
|
||||
@@ -486,7 +486,7 @@
|
||||
Digital items cannot be cancelled or edited after receiving
|
||||
payment.
|
||||
</p>
|
||||
<h4>16. Pricing & availability</h4>
|
||||
<h3>16. Pricing & availability</h3>
|
||||
<p>
|
||||
All prices are shown in Australia dollars (AUD). All items are
|
||||
subject to availability and we reserve the right to impose
|
||||
@@ -497,7 +497,7 @@
|
||||
from those in the store or from store-advertised prices. All
|
||||
purchases on applicable products include GST at the rate of 10%.
|
||||
</p>
|
||||
<h4>17. Errors</h4>
|
||||
<h3>17. Errors</h3>
|
||||
<p>
|
||||
We attempt to be as accurate as possible and eliminate errors on
|
||||
the Sites; however, we do not warrant that any product, service,
|
||||
@@ -511,7 +511,7 @@
|
||||
any amount charged. Your sole remedy in the event of such error
|
||||
is to cancel your order and obtain a refund.
|
||||
</p>
|
||||
<h4>18. Out of stock / pre-order items</h4>
|
||||
<h3>18. Out of stock / pre-order items</h3>
|
||||
<p>
|
||||
If the colour or size you want is not listed in the "Choose Your
|
||||
Colour/Size" drop-down box on the Product Information page, it
|
||||
@@ -527,10 +527,10 @@
|
||||
the availability of that item. If you have items on pre-order
|
||||
that you would like to cancel, please contact us.
|
||||
</p>
|
||||
<h4>
|
||||
<h3>
|
||||
19. Agreement to Conduct Transactions Electronically; Recording;
|
||||
Copies
|
||||
</h4>
|
||||
</h3>
|
||||
<p>
|
||||
You agree that all of your transactions with or through the
|
||||
Sites may, at our option, be conducted electronically from start
|
||||
@@ -542,7 +542,7 @@
|
||||
other contract or disclosure that we are required to provide to
|
||||
you.
|
||||
</p>
|
||||
<h4>20. Payment</h4>
|
||||
<h3>20. Payment</h3>
|
||||
<p>
|
||||
We currently accept Visa and Mastercard online. Only valid
|
||||
credit cards or other payment method acceptable to us may be
|
||||
@@ -556,7 +556,7 @@
|
||||
otherwise acceptable, your order may be suspended or cancelled
|
||||
automatically.
|
||||
</p>
|
||||
<h4>21. Third-party sellers / on-sellers (buying & selling)</h4>
|
||||
<h3>21. Third-party sellers / on-sellers (buying & selling)</h3>
|
||||
<p>
|
||||
You may not place orders with the intention to immediately
|
||||
on-forward the products to another person in a business
|
||||
@@ -592,5 +592,17 @@
|
||||
but this is allowed under the Law.
|
||||
</p>
|
||||
</template>
|
||||
</SMPage>
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SMMastHead from "../components/SMMastHead.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-terms-and-conditions {
|
||||
h3 {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SMMastHead title="Workshops" />
|
||||
<SMContainer>
|
||||
<SMToolbar>
|
||||
<SMToolbar class="align-items-start">
|
||||
<SMInput
|
||||
v-model="filterKeywords"
|
||||
label="Keywords"
|
||||
@@ -10,12 +10,14 @@
|
||||
<SMInput
|
||||
v-model="filterLocation"
|
||||
label="Location"
|
||||
:show-clear="true"
|
||||
@change="handleFilter" />
|
||||
<SMInput
|
||||
v-model="filterDateRange"
|
||||
type="daterange"
|
||||
label="Date Range"
|
||||
:feedback-invalid="dateRangeError"
|
||||
:show-clear="true"
|
||||
@change="handleFilter" />
|
||||
</SMToolbar>
|
||||
<SMPagination
|
||||
@@ -30,9 +32,9 @@
|
||||
:message="formMessage"
|
||||
class="mt-5" />
|
||||
|
||||
<div class="events-list">
|
||||
<div v-if="postsTotal > 0" class="events">
|
||||
<router-link
|
||||
class="event"
|
||||
class="event-card"
|
||||
v-for="event in events"
|
||||
:key="event.id"
|
||||
:to="{ name: 'event', params: { id: event.id } }">
|
||||
@@ -62,6 +64,7 @@
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<SMNoItems v-else />
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
@@ -76,6 +79,7 @@ import { Event, EventCollection } from "../helpers/api.types";
|
||||
import { SMDate } from "../helpers/datetime";
|
||||
import SMMastHead from "../components/SMMastHead.vue";
|
||||
import SMContainer from "../components/SMContainer.vue";
|
||||
import SMNoItems from "../components/SMNoItems.vue";
|
||||
|
||||
interface EventData {
|
||||
event: Event;
|
||||
@@ -87,7 +91,7 @@ const loading = ref(true);
|
||||
let events: Event[] = reactive([]);
|
||||
const dateRangeError = ref("");
|
||||
|
||||
const formMessage = ref("");
|
||||
const formMessage = ref("123");
|
||||
|
||||
const filterKeywords = ref("");
|
||||
const filterLocation = ref("");
|
||||
@@ -330,13 +334,13 @@ handleLoad();
|
||||
|
||||
<style lang="scss">
|
||||
.page-workshops {
|
||||
.events-list {
|
||||
.events {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
width: 100%;
|
||||
|
||||
.event {
|
||||
.event-card {
|
||||
background-color: var(--base-color-light);
|
||||
box-shadow: 0 5px 10px -3px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 8px;
|
||||
@@ -359,6 +363,7 @@ handleLoad();
|
||||
.title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -387,7 +392,7 @@ handleLoad();
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-workshops {
|
||||
.events-list {
|
||||
.events {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -395,43 +400,9 @@ handleLoad();
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.page-workshops {
|
||||
.events-list {
|
||||
.events {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .sm-page-workshop-list {
|
||||
// background-color: #f8f8f8;
|
||||
|
||||
// .toolbar {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// flex: 1;
|
||||
|
||||
// & > * {
|
||||
// padding-left: map-get($spacer, 1);
|
||||
// padding-right: map-get($spacer, 1);
|
||||
|
||||
// &:first-child {
|
||||
// padding-left: 0;
|
||||
// }
|
||||
|
||||
// &:last-child {
|
||||
// padding-right: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// @media screen and (max-width: 768px) {
|
||||
// .sm-page-workshop-list .toolbar {
|
||||
// flex-direction: column;
|
||||
|
||||
// & > * {
|
||||
// padding-left: 0;
|
||||
// padding-right: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -1,67 +1,70 @@
|
||||
<template>
|
||||
<SMMastHead title="Dashboard" />
|
||||
<div class="boxes">
|
||||
<router-link to="/dashboard/details" class="box">
|
||||
<ion-icon name="location-outline" />
|
||||
<h2>My Details</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/posts')"
|
||||
:to="{ name: 'dashboard-post-list' }"
|
||||
class="box">
|
||||
<ion-icon name="newspaper-outline" />
|
||||
<h2>Posts</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/users')"
|
||||
:to="{ name: 'dashboard-user-list' }"
|
||||
class="box">
|
||||
<ion-icon name="people-outline" />
|
||||
<h2>Users</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/events')"
|
||||
:to="{ name: 'dashboard-event-list' }"
|
||||
class="box">
|
||||
<ion-icon name="calendar-outline" />
|
||||
<h2>Events</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/courses')"
|
||||
:to="{ name: 'dashboard-event-list' }"
|
||||
class="box">
|
||||
<ion-icon name="school-outline" />
|
||||
<h2>{{ courseBoxTitle }}</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/media')"
|
||||
:to="{ name: 'dashboard-media-list' }"
|
||||
class="box">
|
||||
<ion-icon name="film-outline" />
|
||||
<h2>Media</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/media')"
|
||||
:to="{ name: 'dashboard-media-list' }"
|
||||
class="box"
|
||||
style="background-image: url('/img/minecraft.png')">
|
||||
<img src="/img/minecraft-grass-block.png" />
|
||||
<h2>Minecraft</h2>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('logs/discord')"
|
||||
:to="{ name: 'dashboard-discord-bot-logs' }"
|
||||
class="box">
|
||||
<ion-icon name="logo-discord" />
|
||||
<h2>Discord Bot Logs</h2>
|
||||
</router-link>
|
||||
</div>
|
||||
<SMContainer>
|
||||
<div class="cards">
|
||||
<router-link to="/dashboard/details" class="admin-card details">
|
||||
<ion-icon name="location-outline" />
|
||||
<h3>My Details</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/posts')"
|
||||
:to="{ name: 'dashboard-post-list' }"
|
||||
class="admin-card posts">
|
||||
<ion-icon name="newspaper-outline" />
|
||||
<h3>Posts</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/users')"
|
||||
:to="{ name: 'dashboard-user-list' }"
|
||||
class="admin-card users">
|
||||
<ion-icon name="people-outline" />
|
||||
<h3>Users</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/events')"
|
||||
:to="{ name: 'dashboard-event-list' }"
|
||||
class="admin-card events">
|
||||
<ion-icon name="calendar-outline" />
|
||||
<h3>Events</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/courses')"
|
||||
:to="{ name: 'dashboard-event-list' }"
|
||||
class="admin-card courses">
|
||||
<ion-icon name="school-outline" />
|
||||
<h3>{{ courseBoxTitle }}</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/media')"
|
||||
:to="{ name: 'dashboard-media-list' }"
|
||||
class="admin-card media">
|
||||
<ion-icon name="film-outline" />
|
||||
<h3>Media</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('admin/media')"
|
||||
:to="{ name: 'dashboard-media-list' }"
|
||||
class="admin-card minecraft"
|
||||
style="background-image: url('/img/minecraft.png')">
|
||||
<img src="/img/minecraft-grass-block.png" />
|
||||
<h3>Minecraft</h3>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="userStore.permissions.includes('logs/discord')"
|
||||
:to="{ name: 'dashboard-discord-bot-logs' }"
|
||||
class="admin-card discord">
|
||||
<ion-icon name="logo-discord" />
|
||||
<h3>Discord Bot Logs</h3>
|
||||
</router-link>
|
||||
</div>
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useUserStore } from "../../store/UserStore";
|
||||
import SMMastHead from "../../components/SMMastHead.vue";
|
||||
import SMContainer from "../../components/SMContainer.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -75,51 +78,36 @@ const courseBoxTitle = computed(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.boxes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin: -#{map-get($spacer, 3)};
|
||||
|
||||
.box {
|
||||
.page-dashboard {
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-basis: map-get($spacer, 5) * 4.5;
|
||||
flex-direction: column;
|
||||
border-radius: 12px;
|
||||
border: 2px solid $primary-color-dark;
|
||||
background-color: #f8f8f8;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
padding: map-get($spacer, 5) map-get($spacer, 4);
|
||||
margin: map-get($spacer, 3);
|
||||
font-size: map-get($spacer, 3);
|
||||
color: $primary-color-dark !important;
|
||||
margin-bottom: map-get($spacer, 5);
|
||||
transition: all 0.2s ease-in-out;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
justify-content: center;
|
||||
|
||||
h2 {
|
||||
margin-top: map-get($spacer, 2);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
font-size: map-get($spacer, 5);
|
||||
}
|
||||
|
||||
img {
|
||||
height: map-get($spacer, 5);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.admin-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 224px;
|
||||
align-items: center;
|
||||
color: var(--base-color-text);
|
||||
border-radius: 10px;
|
||||
background-color: var(--base-color-light);
|
||||
text-decoration: none;
|
||||
background-color: $primary-color-lighter;
|
||||
border-color: $primary-color-darker;
|
||||
color: $primary-color-darker !important;
|
||||
box-shadow: 0 0 14px rgba(0, 0, 0, 0.25);
|
||||
transform: scale(1.01);
|
||||
box-shadow: var(--base-shadow);
|
||||
padding: 32px;
|
||||
|
||||
ion-icon {
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
&.minecraft {
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,66 @@
|
||||
<template>
|
||||
<SMPage class="sm-page-user-edit">
|
||||
<template #container>
|
||||
<SMHeading :heading="pageHeading" />
|
||||
<SMForm :model-value="form" @submit="handleSubmit">
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="first_name" /></SMColumn>
|
||||
<SMColumn><SMInput control="last_name" /></SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="email" /></SMColumn>
|
||||
<SMColumn><SMInput control="phone" /></SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn>
|
||||
<SMFormFooter>
|
||||
<template #right>
|
||||
<SMButton
|
||||
type="secondary"
|
||||
label="Change Password"
|
||||
@click="handleChangePassword" />
|
||||
<SMButton type="submit" label="Update" />
|
||||
</template>
|
||||
</SMFormFooter>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
</SMForm>
|
||||
</template>
|
||||
</SMPage>
|
||||
<SMMastHead
|
||||
:title="pageHeading"
|
||||
:back-link="{ name: 'dashboard' }"
|
||||
back-title="Back to Dashboard" />
|
||||
<SMContainer>
|
||||
<SMForm :model-value="form" @submit="handleSubmit">
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="username" disabled /></SMColumn>
|
||||
<SMColumn><SMInput control="display_name" /></SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="first_name" /></SMColumn>
|
||||
<SMColumn><SMInput control="last_name" /></SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="email" /></SMColumn>
|
||||
<SMColumn
|
||||
><SMInput control="phone">This field is optional</SMInput>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn>
|
||||
<SMFormFooter>
|
||||
<template #right>
|
||||
<SMButton
|
||||
type="secondary"
|
||||
label="Change Password"
|
||||
@click="handleChangePassword" />
|
||||
<SMButton type="submit" label="Update" />
|
||||
</template>
|
||||
</SMFormFooter>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
</SMForm>
|
||||
</SMContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { openDialog } from "../../components/SMDialog";
|
||||
import SMDialogChangePassword from "../../components/dialogs/SMDialogChangePassword.vue";
|
||||
import SMButton from "../../components/SMButton.vue";
|
||||
import SMForm from "../../components/SMForm.vue";
|
||||
import SMFormFooter from "../../components/SMFormFooter.vue";
|
||||
import SMHeading from "../../components/SMHeading.vue";
|
||||
import SMInput from "../../depreciated/SMInput-old.vue";
|
||||
import SMInput from "../../components/SMInput.vue";
|
||||
import { api } from "../../helpers/api";
|
||||
import { UserResponse } from "../../helpers/api.types";
|
||||
import { Form, FormControl } from "../../helpers/form";
|
||||
import { And, Email, Phone, Required } from "../../helpers/validate";
|
||||
import { useUserStore } from "../../store/UserStore";
|
||||
import SMMastHead from "../../components/SMMastHead.vue";
|
||||
import { useToastStore } from "../../store/ToastStore";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
let form = reactive(
|
||||
Form({
|
||||
username: FormControl("", And([Required()])),
|
||||
display_name: FormControl("", And([Required()])),
|
||||
first_name: FormControl("", And([Required()])),
|
||||
last_name: FormControl("", And([Required()])),
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
@@ -65,7 +76,7 @@ const loadData = async () => {
|
||||
try {
|
||||
form.loading(true);
|
||||
const result = await api.get({
|
||||
url: "users/{id}",
|
||||
url: "/users/{id}",
|
||||
params: {
|
||||
id: route.params.id,
|
||||
},
|
||||
@@ -85,6 +96,7 @@ const loadData = async () => {
|
||||
form.loading(false);
|
||||
}
|
||||
} else {
|
||||
form.controls.username.value = userStore.username;
|
||||
form.controls.first_name.value = userStore.firstName;
|
||||
form.controls.last_name.value = userStore.lastName;
|
||||
form.controls.phone.value = userStore.phone;
|
||||
@@ -99,7 +111,7 @@ const handleSubmit = async () => {
|
||||
try {
|
||||
form.loading(true);
|
||||
const result = await api.put({
|
||||
url: "users/{id}",
|
||||
url: "/users/{id}",
|
||||
params: {
|
||||
id: userStore.id,
|
||||
},
|
||||
@@ -117,7 +129,15 @@ const handleSubmit = async () => {
|
||||
userStore.setUserDetails(data.user);
|
||||
}
|
||||
|
||||
form.message("Your details have been updated", "success");
|
||||
useToastStore().addToast({
|
||||
title: route.params.id ? "Details Updated" : "User Created",
|
||||
content: route.params.id
|
||||
? "The user has been updated."
|
||||
: "The user has been created.",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
router.push({ name: "dashboard" });
|
||||
} catch (err) {
|
||||
form.apiErrors(err);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user