This commit is contained in:
2023-02-27 16:08:41 +10:00
parent c8e49ba49c
commit 1ee2a1189d
18 changed files with 78 additions and 226 deletions

View File

@@ -30,7 +30,7 @@ defineProps({
align-self: center;
.sm-message {
display: inline-flex;
display: flex;
padding: map-get($spacer, 2) map-get($spacer, 3);
margin-bottom: map-get($spacer, 4);
text-align: center;
@@ -69,6 +69,7 @@ defineProps({
justify-content: center;
align-self: center;
white-space: pre-wrap;
flex: 1;
}
}
}

View File

@@ -87,7 +87,7 @@ const menuItems = [
},
{
name: "contact",
label: "Contact us",
label: "Contact",
to: { name: "contact" },
icon: "mail-outline",
},

View File

@@ -20,7 +20,7 @@ interface ApiOptions {
export interface ApiResponse {
status: number;
message: string;
data: unknown;
data: Record<string, unknown>;
json?: Record<string, unknown>;
}

View File

@@ -64,3 +64,8 @@ export interface UserResponse {
export interface UserCollection {
users: Array<User>;
}
export interface LoginResponse {
user: User;
token: string;
}

View File

@@ -9,9 +9,9 @@ import {
type FormObjectValidateFunction = (item: string | null) => boolean;
type FormObjectLoadingFunction = (state: boolean) => void;
type FormObjectMessageFunction = (
message: string,
type: string,
icon: string
message?: string,
type?: string,
icon?: string
) => void;
type FormObjectErrorFunction = (message: string) => void;
type FormObjectApiErrorsFunction = (apiErrors: ApiResponse) => void;
@@ -77,21 +77,19 @@ const defaultFormObject: FormObject = {
let foundKeys = false;
if (
apiResponse.json &&
typeof apiResponse.json === "object" &&
apiResponse.json.errors
apiResponse.data &&
typeof apiResponse.data === "object" &&
apiResponse.data.errors
) {
const errors = apiResponse.json.errors as Record<string, string>;
const errors = apiResponse.data.errors as Record<string, string>;
Object.keys(errors).forEach((key) => {
if (
typeof this[key] === "object" &&
Object.keys(this[key]).includes("validation")
typeof this.controls[key] === "object" &&
Object.keys(this.controls[key]).includes("validation")
) {
foundKeys = true;
this[key].validation.result = createValidationResult(
false,
errors[key]
);
this.controls[key].validation.result =
createValidationResult(false, errors[key]);
}
});
}

View File

@@ -124,7 +124,7 @@ export const routes = [
path: "/contact",
name: "contact",
meta: {
title: "Contact Us",
title: "Contact",
},
component: () => import("@/views/Contact.vue"),
},

View File

@@ -1,17 +0,0 @@
import "pinia";
import { UserDetails } from "./UserStore";
declare module "pinia" {
export interface PiniaCustomProperties {
setUserDetails(user: UserDetails): void;
id: string;
token: string;
username: string;
firstName: string;
lastName: string;
email: string;
phone: string;
permissions: string[];
}
}

View File

@@ -1,4 +1,4 @@
import { defineStore } from "pinia";
import { defineStore, DefineStoreOptions } from "pinia";
export interface UserDetails {
id: string;
@@ -21,7 +21,7 @@ export interface UserState {
permissions: string[];
}
export const useUserStore = defineStore<string, UserState>({
export const useUserStore = defineStore({
id: "user",
state: (): UserState => ({
id: "",
@@ -62,4 +62,4 @@ export const useUserStore = defineStore<string, UserState>({
},
persist: true,
});
} as DefineStoreOptions<string, unknown, unknown, unknown> & { persist?: boolean });

View File

@@ -1,5 +0,0 @@
<template>
<div>
<div>About</div>
</div>
</template>

View File

@@ -60,7 +60,7 @@
<template v-else>
<h1>Message Sent!</h1>
<p class="text-center">
Your message as been sent to us. We will respond
Your message as been sent.<br />We will respond
as soon as we can.
</p>
<SMButton
@@ -76,22 +76,20 @@
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { useReCaptcha } from "vue-recaptcha-v3";
import SMButton from "../components/SMButton.vue";
import SMDialog from "../components/SMDialog.vue";
import SMForm from "../components/SMForm.vue";
import SMInput from "../components/SMInput.vue";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Email, Min, Required } from "../helpers/validate";
import { reactive, ref } from "vue";
import { useReCaptcha } from "vue-recaptcha-v3";
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
const form = reactive(
Form({
name: FormControl("", And([Required(), Min(4)])),
name: FormControl("", And([Required()])),
email: FormControl("", And([Required(), Email()])),
content: FormControl("", And([Required(), Min(8)])),
})
@@ -108,20 +106,19 @@ const handleSubmit = async () => {
await api.post({
url: "/contact",
body: {
name: form.name.value,
email: form.email.value,
name: form.controls.name.value,
email: form.controls.email.value,
captcha_token: captcha,
content: form.content.value,
content: form.controls.content.value,
},
});
formSubmitted.value = true;
} catch (err) {
console.log(err);
form.apiErrors(err);
} catch (error) {
form.error("A captcha error occurred. Try reloading the page.");
} finally {
form.loading(false);
}
form.loading(false);
};
</script>

View File

@@ -1,50 +0,0 @@
<template>
<div ref="root" :class="classes">
<div class="mdialog">
<button @click="confimer">Close</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { transitionEnter, transitionLeave } from "../helpers/transition";
const root = ref(null);
const classes = ref(["mdialog-mask", "fade-enter-from"]);
const props = defineProps({ title: "" });
let data = {
title: props.title,
};
const emit = defineEmits(["confirm", "cancel"]);
const confimer = () => {
transitionLeave(root, "fade", () => {
emit("confirm", {});
});
};
transitionEnter(root, "fade");
</script>
<style lang="scss">
.mdialog-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.mdialog {
padding: 1rem 2rem;
background-color: white;
}
</style>

View File

@@ -1,19 +0,0 @@
<template>
<SMDialog>
<SMButton type="primary" label="Confirm" @click="onClick" />
</SMDialog>
</template>
<script setup lang="ts">
import SMDialog from "./Dialog.vue";
import SMButton from "../components/SMButton.vue";
const emit = defineEmits(["confirm", "cancel"]);
const onClick = () => {
console.log("click");
emit("confirm");
};
</script>
<style lang="scss"></style>

View File

@@ -48,7 +48,6 @@ import SMDialog from "../components/SMDialog.vue";
import SMForm from "../components/SMForm.vue";
import SMFormFooter from "../components/SMFormFooter.vue";
import SMInput from "../components/SMInput.vue";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Max, Min, Required } from "../helpers/validate";
@@ -71,21 +70,30 @@ const handleSubmit = async () => {
await api.post({
url: "/users/verifyEmail",
body: {
code: form.code.value,
code: form.controls.code.value,
captcha_token: captcha,
},
});
formDone.value = true;
} catch (err) {
form.apiErrors(err);
} catch (error) {
form.apiErrors(error);
} finally {
form.loading(false);
}
form.loading(false);
};
if (useRoute().query.code !== undefined) {
form.code.value = useRoute().query.code;
const code = useRoute().query.code;
if (Array.isArray(code)) {
if (code.length > 0) {
form.controls.code.value = code[0];
}
} else {
form.controls.code.value = code;
}
handleSubmit();
}
</script>

View File

@@ -1,56 +0,0 @@
<template>
<SMContainer class="dashboard mx-auto workshops">
<h1>Workshops</h1>
<SMMessage
v-if="formMessage.message"
:icon="formMessage.icon"
:type="formMessage.type"
:message="formMessage.message"
class="mt-5" />
<SMPanelList>
<SMPanel
v-for="event in events.value"
:key="event.id"
:to="{ name: 'event', params: { slug: event.id } }"
:title="event.title"
:date="event.start_at"></SMPanel>
</SMPanelList>
</SMContainer>
</template>
<script setup lang="ts">
import SMMessage from "../components/SMMessage.vue";
import SMPanelList from "../components/SMPanelList.vue";
import SMPanel from "../components/SMPanel.vue";
import { reactive } from "vue";
import { api } from "../helpers/api";
const events = reactive([]);
const formMessage = reactive({
icon: "",
type: "",
message: "",
});
const handleLoad = async () => {
formMessage.type = "error";
formMessage.icon = "alert-circle-outline";
formMessage.message = "";
try {
let result = await api.get({
url: "/events",
params: {
limit: 10,
},
});
events.value = result.json.events;
} catch (error) {
formMessage.message =
error.json?.message || "Could not load any events from the server.";
}
};
handleLoad();
</script>

View File

@@ -45,14 +45,13 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { useReCaptcha } from "vue-recaptcha-v3";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Min, Required } from "../helpers/validate";
import SMButton from "../components/SMButton.vue";
import SMDialog from "../components/SMDialog.vue";
import SMFormFooter from "../components/SMFormFooter.vue";
import SMInput from "../components/SMInput.vue";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Min, Required } from "../helpers/validate";
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
const formDone = ref(false);
@@ -72,7 +71,7 @@ const handleSubmit = async () => {
await api.post({
url: "/users/forgotPassword",
body: {
username: form.username.value,
username: form.controls.username.value,
captcha_token: captcha,
},
});
@@ -84,8 +83,8 @@ const handleSubmit = async () => {
} else {
form.apiErrors(error);
}
} finally {
form.loading(false);
}
form.loading(false);
};
</script>

View File

@@ -43,17 +43,15 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Email, Required } from "../helpers/validate";
import { useReCaptcha } from "vue-recaptcha-v3";
import SMButton from "../components/SMButton.vue";
import SMDialog from "../components/SMDialog.vue";
import SMForm from "../components/SMForm.vue";
import SMFormFooter from "../components/SMFormFooter.vue";
import SMInput from "../components/SMInput.vue";
import { useReCaptcha } from "vue-recaptcha-v3";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { And, Email, Required } from "../helpers/validate";
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
const formDone = ref(false);
@@ -73,7 +71,7 @@ const handleSubmit = async () => {
await api.post({
url: "/users/forgotUsername",
body: {
email: form.email.value,
email: form.controls.email.value,
captcha_token: captcha,
},
});

View File

@@ -1,7 +0,0 @@
<template>
<div>
<div>Home</div>
</div>
</template>
<script setup lang="ts"></script>

View File

@@ -40,8 +40,8 @@ import SMDialog from "../components/SMDialog.vue";
import SMForm from "../components/SMForm.vue";
import SMFormFooter from "../components/SMFormFooter.vue";
import SMInput from "../components/SMInput.vue";
import { api } from "../helpers/api";
import { LoginResponse } from "../helpers/api.types";
import { Form, FormControl } from "../helpers/form";
import { And, Min, Required } from "../helpers/validate";
import { useUserStore } from "../store/UserStore";
@@ -55,39 +55,39 @@ const form = reactive(
})
);
const redirect = useRoute().query.redirect;
const redirectQuery = useRoute().query.redirect;
const handleSubmit = async () => {
form.message();
form.loading(true);
try {
let res = await api.post({
let result = await api.post({
url: "/login",
body: {
username: form.username.value,
password: form.password.value,
username: form.controls.username.value,
password: form.controls.password.value,
},
});
userStore.setUserDetails(res.data.user);
userStore.setUserToken(res.data.token);
if (redirect !== undefined) {
if (redirect.startsWith("api/")) {
window.location.href =
redirect + "?token=" + encodeURIComponent(res.data.token);
} else {
router.push({ path: redirect });
}
const login = result.data as unknown as LoginResponse;
userStore.setUserDetails(login.user);
userStore.setUserToken(login.token);
if (redirectQuery !== undefined) {
const redirect = Array.isArray(redirectQuery)
? redirectQuery[0]
: redirectQuery;
router.push({ path: redirect });
} else {
router.push({ name: "dashboard" });
}
} catch (err) {
console.log(err);
form.apiErrors(err);
} finally {
form.loading(false);
}
form.loading(false);
};
if (userStore.token) {