remove usernames

This commit is contained in:
2023-05-08 10:40:48 +10:00
parent 7a4f72378d
commit ac2dd23ad7
43 changed files with 372 additions and 864 deletions

View File

@@ -288,6 +288,16 @@ export const routes = [
component: () =>
import("@/views/dashboard/UserList.vue"),
},
{
path: "create",
name: "dashboard-user-create",
meta: {
title: "Create User",
middleware: "authenticated",
},
component: () =>
import("@/views/dashboard/UserEdit.vue"),
},
{
path: ":id",
name: "dashboard-user-edit",

View File

@@ -8,7 +8,7 @@
</h1>
<SMToolbar>
<div>
<div class="author">By {{ article.user.username }}</div>
<div class="author">By {{ article.user.display_name }}</div>
<div class="date">{{ formattedDate(article.publish_at) }}</div>
</div>
<SMButton
@@ -47,7 +47,7 @@ const applicationStore = useApplicationStore();
*/
let article: Ref<Article> = ref({
title: "",
user: { username: "" },
user: { display_name: "" },
});
/**

View File

@@ -5,11 +5,10 @@
<template v-if="!formDone">
<h1>Forgot Password</h1>
<p>
Enter your username below to receive a password reset
link to your email address.
Enter your email below to receive a password reset link.
</p>
<SMForm v-model="form" @submit="handleSubmit">
<SMInput control="username" />
<SMInput control="email" />
<SMButtonRow>
<template #left>
<div class="small">
@@ -31,9 +30,9 @@
<template v-else>
<h1>Email Sent!</h1>
<p class="text-center">
If that username has been registered, you will receive
an email with a reset password link in the next few
minutes.
If that email address has been registered, you will
receive an email with a reset password link in the next
few minutes.
</p>
<SMRow class="pb-2">
<SMColumn class="justify-content-center">
@@ -56,13 +55,13 @@ import SMButtonRow from "../components/SMButtonRow.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";
import { And, Email, Required } from "../helpers/validate";
// const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
const formDone = ref(false);
let form = reactive(
Form({
username: FormControl("", And([Required(), Min(4)])),
email: FormControl("", And([Required(), Email()])),
})
);
@@ -76,7 +75,7 @@ const handleSubmit = async () => {
await api.post({
url: "/users/forgotPassword",
body: {
username: form.controls.username.value,
email: form.controls.email.value,
// captcha_token: captcha,
},
});

View File

@@ -1,90 +0,0 @@
<template>
<SMPage>
<SMRow>
<SMFormCard class="mt-5">
<template v-if="!formDone">
<h1>Forgot Username</h1>
<p>
Enter your email address, and if an account exists, we
will email you your username.
</p>
<SMForm v-model="form" @submit="handleSubmit">
<SMInput control="email" />
<SMButtonRow>
<template #left>
<div class="small">
<span class="pr-1">Remember?</span
><router-link :to="{ name: 'login' }"
>Log in</router-link
>
</div>
</template>
<template #right>
<SMButton
type="submit"
label="Send"
icon="arrow-forward-outline" />
</template>
</SMButtonRow>
</SMForm>
</template>
<template v-else>
<h1>Email Sent!</h1>
<p class="text-center">
If that email has a registered account, you should
receive it shortly.
</p>
<SMRow class="pb-2">
<SMColumn class="justify-content-center">
<SMButton :to="{ name: 'home' }" label="Home" />
</SMColumn>
</SMRow>
</template>
</SMFormCard>
</SMRow>
</SMPage>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
// import { useReCaptcha } from "vue-recaptcha-v3";
import SMButton from "../components/SMButton.vue";
import SMFormCard from "../components/SMFormCard.vue";
import SMForm from "../components/SMForm.vue";
import SMButtonRow from "../components/SMButtonRow.vue";
import SMInput from "../components/SMInput.vue";
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);
let form = reactive(
Form({
email: FormControl("", And([Required(), Email()])),
})
);
const handleSubmit = async () => {
form.loading(true);
try {
// await recaptchaLoaded();
// const captcha = await executeRecaptcha("submit");
await api.post({
url: "/users/forgotUsername",
body: {
email: form.controls.email.value,
// captcha_token: captcha,
},
});
formDone.value = true;
} catch (error) {
form.apiErrors(error);
}
form.loading(false);
};
</script>

View File

@@ -9,11 +9,7 @@
</p>
</template>
<template #body>
<SMInput control="username" autofocus>
<router-link to="/forgot-username"
>Forgot username?</router-link
>
</SMInput>
<SMInput control="email" autofocus> </SMInput>
<SMInput control="password" type="password">
<router-link to="/forgot-password"
>Forgot password?</router-link
@@ -54,7 +50,7 @@ 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 { And, Email, Required } from "../helpers/validate";
import { useUserStore } from "../store/UserStore";
import SMButtonRow from "../components/SMButtonRow.vue";
@@ -63,7 +59,7 @@ const userStore = useUserStore();
const router = useRouter();
let form = reactive(
Form({
username: FormControl("", And([Required(), Min(4)])),
email: FormControl("", And([Required(), Email()])),
password: FormControl("", Required()),
})
);
@@ -81,7 +77,7 @@ const handleSubmit = async () => {
let result = await api.post({
url: "/login",
body: {
username: form.controls.username.value,
email: form.controls.email.value,
password: form.controls.password.value,
},
});

View File

@@ -1,7 +1,7 @@
<template>
<SMContainer :center="true">
<SMForm v-if="!userRegistered" v-model="form" @submit="handleSubmit">
<SMFormCard full>
<SMFormCard>
<template #header>
<h2>Register</h2>
<p>
@@ -10,36 +10,9 @@
</p>
</template>
<template #body>
<SMRow>
<SMColumn>
<SMInput control="username" autofocus />
</SMColumn>
<SMColumn>
<SMInput
control="password"
type="password"></SMInput>
</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"
><template #help
>This field is optional</template
>
</SMInput>
</SMColumn>
</SMRow>
<SMInput control="email" autofocus />
<SMInput control="password" type="password" />
<SMInput control="display_name" label="Display Name" />
</template>
<template #footer-space-between>
<div class="small">
@@ -125,7 +98,6 @@ let form = reactive(
first_name: FormControl("", Required()),
last_name: FormControl("", Required()),
email: FormControl("", And([Required(), Email()])),
phone: FormControl("", Phone()),
username: FormControl("", And([Min(4), Custom(checkUsername)])),
password: FormControl("", And([Required(), Password()])),
})
@@ -135,20 +107,14 @@ const handleSubmit = async () => {
form.loading(true);
try {
// await recaptchaLoaded();
// const captcha = await executeRecaptcha("submit");
await api.post({
url: "/register",
body: {
first_name: form.controls.first_name.value,
last_name: form.controls.last_name.value,
email: form.controls.email.value,
phone: form.controls.phone.value,
username: form.controls.username.value,
password: form.controls.password.value,
display_name: form.controls.username.value,
// captcha_token: captcha,
display_name: form.controls.display_name.value,
},
});

View File

@@ -5,7 +5,7 @@
<template v-if="!formDone">
<h1>Resend Verify Email</h1>
<SMForm v-model="form" @submit="handleSubmit">
<SMInput control="username" />
<SMInput control="email" />
<SMButtonRow>
<template #left>
<div class="small">
@@ -27,9 +27,9 @@
<template v-else>
<h1>Email Sent!</h1>
<p class="text-center">
If that username has been registered, and you still need
to verify your email, you will receive an email with a
new verify code.
If that email address has been registered, and you still
need to verify your email, you will receive an email
with a new verify code.
</p>
<SMButtonRow>
<template #right>
@@ -52,13 +52,13 @@ import SMButtonRow from "../components/SMButtonRow.vue";
import SMInput from "../components/SMInput.vue";
import { api } from "../helpers/api";
import { Form, FormControl } from "../helpers/form";
import { Required } from "../helpers/validate";
import { And, Email, Required } from "../helpers/validate";
// const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
const formDone = ref(false);
let form = reactive(
Form({
username: FormControl("", Required()),
email: FormControl("", And([Required(), Email()])),
})
);
@@ -72,7 +72,7 @@ const handleSubmit = async () => {
await api.post({
url: "/users/resendVerifyEmailCode",
body: {
username: form.controls.username.value,
email: form.controls.email.value,
// captcha_token: captcha,
},
});

View File

@@ -301,7 +301,7 @@ const loadOptionsAuthors = async () => {
api.get({
url: "/users",
params: {
fields: "id,username,first_name,last_name",
fields: "id,display_name",
limit: 100,
},
})
@@ -312,7 +312,7 @@ const loadOptionsAuthors = async () => {
authors.value = {};
data.users.forEach((item) => {
authors.value[item.id] = `${item.username}`;
authors.value[item.id] = `${item.display_name}`;
});
}
})

View File

@@ -2,15 +2,18 @@
<SMMastHead
:title="pageHeading"
:back-link="
route.params.id
route.params.id || isCreatingUser
? { name: 'dashboard-user-list' }
: { name: 'dashboard' }
"
:back-title="route.params.id ? 'Back to Users' : 'Back to Dashboard'" />
:back-title="
route.params.id || isCreatingUser
? 'Back to Users'
: '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>
@@ -90,9 +93,10 @@ const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const isCreatingUser = route.path.endsWith("/create");
let form = reactive(
Form({
username: FormControl("", And([Required()])),
display_name: FormControl("", And([Required()])),
first_name: FormControl("", And([Required()])),
last_name: FormControl("", And([Required()])),
@@ -122,7 +126,6 @@ const loadData = async () => {
const data = result.data as UserResponse;
if (data && data.user) {
form.controls.username.value = data.user.username;
form.controls.first_name.value = data.user.first_name;
form.controls.last_name.value = data.user.last_name;
form.controls.display_name.value = data.user.display_name;
@@ -134,8 +137,7 @@ const loadData = async () => {
} finally {
form.loading(false);
}
} else {
form.controls.username.value = userStore.username;
} else if (isCreatingUser == false) {
form.controls.first_name.value = userStore.firstName;
form.controls.last_name.value = userStore.lastName;
form.controls.display_name.value = userStore.displayName;
@@ -150,34 +152,56 @@ const loadData = async () => {
const handleSubmit = async () => {
try {
form.loading(true);
const result = await api.put({
url: "/users/{id}",
params: {
id: userStore.id,
},
body: {
first_name: form.controls.first_name.value,
last_name: form.controls.last_name.value,
display_name: form.controls.display_name.value,
email: form.controls.email.value,
phone: form.controls.phone.value,
},
});
const id = route.params.id ? route.params.id : userStore.id;
const data = result.data as UserResponse;
if (isCreatingUser == false) {
const result = await api.put({
url: "/users/{id}",
params: {
id: id,
},
body: {
first_name: form.controls.first_name.value,
last_name: form.controls.last_name.value,
display_name: form.controls.display_name.value,
email: form.controls.email.value,
phone: form.controls.phone.value,
},
});
if (data && data.user) {
userStore.setUserDetails(data.user);
const data = result.data as UserResponse;
if (route.params.id && data && data.user) {
userStore.setUserDetails(data.user);
}
useToastStore().addToast({
title: "Details Updated",
content: "The user has been updated.",
type: "success",
});
} else {
await api.post({
url: "/users",
params: {
id: id,
},
body: {
first_name: form.controls.first_name.value,
last_name: form.controls.last_name.value,
display_name: form.controls.display_name.value,
email: form.controls.email.value,
phone: form.controls.phone.value,
},
});
useToastStore().addToast({
title: "User Created",
content: "The user has been created.",
type: "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);

View File

@@ -1,91 +1,154 @@
<template>
<SMPage permission="admin/users" :page-error="pageError">
<SMPage permission="admin/users">
<SMMastHead
title="Users"
:back-link="{ name: 'dashboard' }"
back-title="Return to Dashboard" />
<SMContainer>
<SMTable
:headers="headers"
:items="items"
@row-click="handleRowClick">
<template #item-actions="item">
<SMButton
label="Edit"
:dropdown="{
download: 'Download',
delete: 'Delete',
}"
size="medium" />
</template>
</SMTable>
<SMContainer class="flex-grow-1">
<SMToolbar>
<SMButton
:to="{ name: 'dashboard-user-create' }"
type="primary"
label="Create User" />
<SMInput
v-model="itemSearch"
label="Search"
class="toolbar-search"
@keyup.enter="handleSearch">
<template #append>
<SMButton
type="primary"
label="Search"
icon="search-outline"
@click="handleSearch" />
</template>
</SMInput>
</SMToolbar>
<SMLoading large v-if="itemsLoading" />
<template v-else>
<SMPagination
v-if="items.length < itemsTotal"
v-model="itemsPage"
:total="itemsTotal"
:per-page="itemsPerPage" />
<SMNoItems v-if="items.length == 0" text="No Media Found" />
<SMTable
:headers="headers"
:items="items"
@row-click="handleEdit">
<template #item-actions="item">
<SMButton
label="Edit"
:dropdown="{
delete: 'Delete',
}"
size="medium"
@click="handleActionButton(item, $event)" />
</template>
</SMTable>
</template>
</SMContainer>
</SMPage>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { openDialog } from "../../components/SMDialog";
import DialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
import { api } from "../../helpers/api";
import { api, getApiResultData } from "../../helpers/api";
import { SMDate } from "../../helpers/datetime";
import SMTable from "../../components/SMTable.vue";
import SMMastHead from "../../components/SMMastHead.vue";
import { useToastStore } from "../../store/ToastStore";
import SMNoItems from "../../components/SMNoItems.vue";
import SMButton from "../../components/SMButton.vue";
import SMInput from "../../components/SMInput.vue";
import SMToolbar from "../../components/SMToolbar.vue";
import { updateRouterParams } from "../../helpers/url";
import { User, UserCollection } from "../../helpers/api.types";
import SMLoading from "../../components/SMLoading.vue";
import SMPagination from "../../components/SMPagination.vue";
const route = useRoute();
const router = useRouter();
const searchValue = ref("");
const pageError = ref(0);
const items = ref([]);
const itemsLoading = ref(false);
const itemSearch = ref((route.query.search as string) || "");
const itemsTotal = ref(0);
const itemsPerPage = 25;
const itemsPage = ref(parseInt((route.query.page as string) || "1"));
const headers = [
{ text: "Username", value: "username", sortable: true },
{ text: "Display name", value: "display_name", sortable: true },
{ text: "First name", value: "first_name", sortable: true },
{ text: "Last name", value: "last_name", sortable: true },
{ text: "Email", value: "email", sortable: true },
{ text: "Phone", value: "phone", sortable: true },
{ text: "Joined", value: "created_at", sortable: true },
// { text: "Last logged in", value: "lastAttended", width: 200},
{ text: "Actions", value: "actions" },
];
const items = ref([]);
const formLoading = ref(false);
const serverItemsLength = ref(0);
const serverOptions = ref({
page: 1,
rowsPerPage: 25,
sortBy: null,
sortType: null,
/**
* Watch if page number changes.
*/
watch(itemsPage, () => {
handleLoad();
});
const handleRowClick = (item) => {
router.push({ name: "dashboard-user-edit", params: { id: item.id } });
/**
* Handle searching for item.
*/
const handleSearch = () => {
itemsPage.value = 1;
handleLoad();
};
const loadFromServer = async () => {
formLoading.value = true;
/**
* Handle user selecting option in action button.
*
* @param {Event} item The event item.
* @param option
*/
const handleActionButton = (item: Event, option: string): void => {
if (option.length == 0) {
handleEdit(item);
} else if (option.toLowerCase() == "delete") {
handleDelete(item);
}
};
/**
* Handle loading the page and list
*/
const handleLoad = async () => {
itemsLoading.value = true;
items.value = [];
itemsTotal.value = 0;
updateRouterParams(router, {
search: itemSearch.value,
page: itemsPage.value == 1 ? "" : itemsPage.value.toString(),
});
try {
let params = {};
if (serverOptions.value.sortBy) {
params["sort"] = serverOptions.value.sortBy;
if (
serverOptions.value.sortType &&
serverOptions.value.sortType === "desc"
) {
params["sort"] = "-" + params["sort"];
}
let params = {
page: itemsPage.value,
limit: itemsPerPage,
};
if (itemSearch.value.length > 0) {
params[
"filter"
] = `title:${itemSearch.value},OR,content:${itemSearch.value}`;
}
params["page"] = serverOptions.value.page;
params["limit"] = serverOptions.value.rowsPerPage;
let res = await api.get({
let result = await api.get({
url: "/users",
params: params,
});
items.value = res.data.users;
const userCollection = getApiResultData<UserCollection>(result);
items.value = userCollection.users;
items.value.forEach((row) => {
if (row.created_at !== "undefined") {
@@ -96,44 +159,22 @@ const loadFromServer = async () => {
}
});
serverItemsLength.value = res.data.total;
itemsTotal.value = userCollection.total;
} catch (err) {
/* empty */
}
formLoading.value = false;
itemsLoading.value = false;
};
loadFromServer();
watch(
serverOptions,
() => {
loadFromServer();
},
{ deep: true }
);
const headerItemClassNameFunction = (header) => {
if (["position", "actions"].includes(header.value))
return "easy-data-table-cell-center";
return "";
};
const bodyItemClassNameFunction = (column) => {
if (["position", "actions"].includes(column))
return "easy-data-table-cell-center";
return "";
};
const handleEdit = (user) => {
const handleEdit = (user: User) => {
router.push({ name: "dashboard-user-edit", params: { id: user.id } });
};
const handleDelete = async (user) => {
const handleDelete = async (user: User) => {
let result = await openDialog(DialogConfirm, {
title: "Delete User?",
text: `Are you sure you want to delete the user <strong>${user.username}</strong>?`,
text: `Are you sure you want to delete the user <strong>${user.display_name}</strong>?`,
cancel: {
type: "secondary",
label: "Cancel",
@@ -147,7 +188,7 @@ const handleDelete = async (user) => {
if (result == true) {
try {
await api.delete(`users${user.id}`);
loadFromServer();
handleLoad();
useToastStore().addToast({
title: "User Deleted",
@@ -163,6 +204,33 @@ const handleDelete = async (user) => {
}
}
};
handleLoad();
</script>
<style lang="scss"></style>
<style lang="scss">
.page-dashboard-user-list {
.toolbar-search {
max-width: 350px;
}
// .table tr {
// td:first-of-type,
// td:nth-of-type(2) {
// word-break: break-all;
// }
// td:not(:first-of-type) {
// white-space: nowrap;
// }
// }
}
@media only screen and (max-width: 768px) {
.page-dashboard-user-list {
.toolbar-search {
max-width: none;
}
}
}
</style>

View File

@@ -57,7 +57,7 @@
</td>
</tr>
<tr>
<td><h2>Hey {{ $user?->username }},</h2></td>
<td><h2>Hey {{ $user?->display_name }},</h2></td>
</tr>
<tr>
<td>

View File

@@ -1,4 +1,4 @@
Hey {{ $user?->username }},
Hey {{ $user?->display_name }},
We just need to confirm that this is your new email address.

View File

@@ -57,7 +57,7 @@
</td>
</tr>
<tr>
<td><h2>Yo {{ $user?->username }}</h2></td>
<td><h2>Yo {{ $user?->display_name }}</h2></td>
</tr>
<tr>
<td style="padding-bottom: 2rem">

View File

@@ -1,4 +1,4 @@
Yo {{ $user?->username }}
Yo {{ $user?->display_name }}
Just a quick word that your email has been changed to {{ $new_email }}.

View File

@@ -57,7 +57,7 @@
</td>
</tr>
<tr>
<td><h2>Yo {{ $user?->username }}</h2></td>
<td><h2>Yo {{ $user?->display_name }}</h2></td>
</tr>
<tr>
<td style="padding-bottom: 2rem">

View File

@@ -1,4 +1,4 @@
Yo {{ $user?->username }}
Yo {{ $user?->display_name }}
Just a quick word that your password has been changed.

View File

@@ -57,7 +57,7 @@
</td>
</tr>
<tr>
<td><h2>Welcome {{ $user?->username }},</h2></td>
<td><h2>Welcome {{ $user?->display_name }},</h2></td>
</tr>
<tr>
<td>

View File

@@ -1,4 +1,4 @@
Welcome {{ $user?->username }},
Welcome {{ $user?->display_name }},
We've heard you would like to try out our workshops and courses!
Before we can let you loose on our website, we need to make sure you are a real person and not a pesky robot or cat.

View File

@@ -57,7 +57,7 @@
</td>
</tr>
<tr>
<td><h2>Yo {{ $user?->username }}</h2></td>
<td><h2>Yo {{ $user?->display_name }}</h2></td>
</tr>
<tr>
<td>

View File

@@ -1,4 +1,4 @@
Yo {{ $user?->username }}
Yo {{ $user?->display_name }}
We all forget things sometimes! But you can reset your password typing the following into your browser https://www.stemmechanics.com.au/reset-password and entering the following code:

View File

@@ -1,131 +0,0 @@
<!DOCTYPE html>
<html
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>STEMMechanics - Forgot Password</title>
<link
rel="noopener"
target="_blank"
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG />
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<style>
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
</style>
</head>
<body>
<table
cellspacing="0"
cellpadding="0"
border="0"
role="presentation"
style="
width: 100%;
padding: 2rem;
font-size: 1.1rem;
color: #000000;
font-family: Nunito, Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
"
>
<tr>
<td>
<a href="https://www.stemmechanics.com.au/">
<img
alt="STEMMechanics Logo"
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
width="400"
height="62"
/>
</a>
</td>
</tr>
<tr>
<td>
@if (count($usernames) > 2)
<h2>Yo {{ $usernames[0] }}, {{ $usernames[1] }}, or is it {{ $usernames[count($usernames)-1] }}?</h2>
@elseif (count($usernames) > 1)
<h2>Yo {{ $usernames[0] }}, or is it {{ $usernames[1] }}?</h2>
@else
<h2>Yo {{ $usernames[0] }},</h2>
@endif
</td>
</tr>
<tr>
<td style="padding-bottom: 2rem;">
@if (count($usernames) == 1)
Guess what, your username is <strong>{{ $usernames[0] }}</strong>.
@else
We have the following usernames registered to this email address:
</td>
</tr>
<tr>
<td style="padding-bottom: 2rem;">
<ul>
@foreach($usernames as $username)
<li>{{ $username }}</li>
@endforeach
</ul>
@endif
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 90%;
text-align: center;
padding-top: 2rem;
padding-bottom: 2rem;
border-top: 1px solid #ddd;
"
>
Need help or got feedback?
<a href="https://www.stemmechanics.com.au/contact"
>Contact us</a
>
or touch base at
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
>.
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 80%;
text-align: center;
padding-top: 1rem;
padding-bottom: 2rem;
"
>
Sent by STEMMechanics &middot;
<a href="https://www.stemmechanics.com.au/"
>Visit our Website</a
>
&middot;
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
><br />PO Box 36, Edmonton, QLD 4869, Australia
</td>
</tr>
</table>
</body>
</html>

View File

@@ -1,24 +0,0 @@
@if (count($usernames) > 2)
Yo {{ $usernames[0] }}, {{ $usernames[1] }}, or is it {{ $usernames[count($usernames)-1] }}?
@elseif (count($usernames) > 1)
Yo {{ $usernames[0] }} or is it {{ $usernames[1] }}?
@else
Yo {{ $usernames[0] }},
@endif
@if (count($usernames) == 1)
Guess what, your username is {{ $usernames[0] }}.
@else
We have the following usernames registered to this email address:
@foreach($usernames as $username)
- {{ $username }}
@endforeach
@endif
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
--
Sent by STEMMechanics
https://www.stemmechanics.com.au/
PO Box 36, Edmonton, QLD 4869, Australia

View File

@@ -1,116 +0,0 @@
<!DOCTYPE html>
<html
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>STEMMechanics - Forgot Password</title>
<link
rel="noopener"
target="_blank"
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG />
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<style>
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
</style>
</head>
<body>
<table
cellspacing="0"
cellpadding="0"
border="0"
role="presentation"
style="
width: 100%;
padding: 2rem;
font-size: 1.1rem;
color: #000000;
font-family: Nunito, Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
"
>
<tr>
<td>
<a href="https://www.stemmechanics.com.au/">
<img
alt="STEMMechanics Logo"
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
width="400"
height="62"
/>
</a>
</td>
</tr>
<tr>
<p></p>
<td><h2>Howdy there,</h2></td>
</tr>
<tr>
<td style="padding-bottom: 2rem">
At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled.
</td>
</tr>
<tr>
<td style="padding-bottom: 2rem">
If this wasn't you, you can unsubscribe by visiting <a href="https://www.stemmechanics.com.au/unsubscribe?email={{ $email }}">stemmechanics.com.au/unsubscribe</a>
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 90%;
text-align: center;
padding-top: 2rem;
padding-bottom: 2rem;
border-top: 1px solid #ddd;
"
>
Need help or got feedback?
<a href="https://www.stemmechanics.com.au/contact"
>Contact us</a
>
or touch base at
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
>.
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 80%;
text-align: center;
padding-top: 1rem;
padding-bottom: 2rem;
"
>
Sent by STEMMechanics &middot;
<a href="https://www.stemmechanics.com.au/"
>Visit our Website</a
>
&middot;
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
><br />PO Box 36, Edmonton, QLD 4869, Australia
</td>
</tr>
</table>
</body>
</html>

View File

@@ -1,14 +0,0 @@
Howdy there,
At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled.
If this wasn't you, you can unsubscribe by visiting the following URL in your browser:
https://www.stemmechanics.com.au/unsubscribe
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
--
Sent by STEMMechanics
https://www.stemmechanics.com.au/
PO Box 36, Edmonton, QLD 4869, Australia

View File

@@ -1,111 +0,0 @@
<!DOCTYPE html>
<html
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>STEMMechanics - Forgot Password</title>
<link
rel="noopener"
target="_blank"
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG />
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<style>
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
</style>
</head>
<body>
<table
cellspacing="0"
cellpadding="0"
border="0"
role="presentation"
style="
width: 100%;
padding: 2rem;
font-size: 1.1rem;
color: #000000;
font-family: Nunito, Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
"
>
<tr>
<td>
<a href="https://www.stemmechanics.com.au/">
<img
alt="STEMMechanics Logo"
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
width="400"
height="62"
/>
</a>
</td>
</tr>
<tr>
<p></p>
<td><h2>Howdy there,</h2></td>
</tr>
<tr>
<td style="padding-bottom: 2rem">
At your request, you are now unsubscribed from our newsletter.
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 90%;
text-align: center;
padding-top: 2rem;
padding-bottom: 2rem;
border-top: 1px solid #ddd;
"
>
Need help or got feedback?
<a href="https://www.stemmechanics.com.au/contact"
>Contact us</a
>
or touch base at
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
>.
</td>
</tr>
<tr>
<td
align="center"
style="
font-size: 80%;
text-align: center;
padding-top: 1rem;
padding-bottom: 2rem;
"
>
Sent by STEMMechanics &middot;
<a href="https://www.stemmechanics.com.au/"
>Visit our Website</a
>
&middot;
<a href="https://twitter.com/stemmechanics"
>@stemmechanics</a
><br />PO Box 36, Edmonton, QLD 4869, Australia
</td>
</tr>
</table>
</body>
</html>

View File

@@ -1,10 +0,0 @@
Howdy there,
At your request, you have been unsubscribed from our newsletter.
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
--
Sent by STEMMechanics
https://www.stemmechanics.com.au/
PO Box 36, Edmonton, QLD 4869, Australia