This commit is contained in:
2023-11-27 08:29:48 +10:00
parent c432b32d08
commit 0772010119
195 changed files with 7153 additions and 9787 deletions

200
resources/css/app.css Normal file
View File

@@ -0,0 +1,200 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
min-height: 100vh;
min-width: 100%;
overflow-x: hidden;
}
input:read-only {
cursor: not-allowed;
background-color: #eee !important;
color: #555 !important;
}
button:disabled,
a.disabled,
input[type="submit"]:disabled {
cursor: not-allowed;
pointer-events: none;
background-color: #ccc !important;
color: #555 !important;
}
button,
input[type="submit"] {
user-select: none;
}
a {
@apply transition hover:text-blue;
}
input {
&[type="text"],
&[type="password"],
&[type="email"] {
@apply w-full rounded border border-gray-300 p-2;
}
}
div.table-layout {
@apply overflow-y-scroll px-4;
table {
@apply mx-auto;
}
}
table {
@apply border-separate border border-[#ccc] rounded-lg bg-[#ddd];
th,
td {
@apply text-sm px-4 whitespace-nowrap;
}
th {
@apply pt-4 pb-3 text-[#666] text-left font-normal border-b border-[#ccc];
}
td {
@apply py-4 bg-[#f8f8f8] text-[#333];
}
tbody tr:not(:last-child) td {
@apply border-b border-[#ddd];
}
tbody tr:last-child {
td:first-child {
@apply rounded-bl-lg;
}
td:last-child {
@apply rounded-br-lg;
}
}
}
.floating-label {
@apply relative my-8;
label {
@apply absolute text-gray-600 z-10 pointer-events-none top-2.5 left-2.5;
transition: all 0.1s ease-in-out;
}
input::placeholder {
@apply opacity-0;
}
input:read-only,
input:focus,
input:valid:not(:placeholder-shown) {
& + label {
@apply -top-4 left-1 text-xs;
}
}
}
input:has(~ .error) {
border: 2px solid #e00000;
}
p.error {
padding-left: 0.25rem;
margin-top: 0.25rem;
font-size: 0.75rem;
color: #e00000;
}
input[type="submit"],
button,
a.btn {
user-select: none;
@apply py-2 px-6 rounded transition inline-block border border-gray-300 hover:bg-gray-300;
&-blue {
@apply btn border-none bg-blue text-white hover:bg-blue-dark;
}
&-green {
@apply btn border-none bg-green text-white hover:bg-green-dark;
}
&-block {
@apply !block text-center;
}
}
.btn-block {
@apply btn !block text-center;
}
.btn-blue {
@apply btn border-none bg-blue text-white hover:bg-blue-dark;
}
.btn-block-blue {
@apply btn-blue !block text-center;
}
.btn-orange {
@apply btn border-none bg-orange text-white hover:bg-orange-dark;
}
.btn-block-orange {
@apply btn-orange !block text-center;
}
.btn-yellow {
@apply btn border-none bg-yellow text-white hover:bg-yellow-dark;
}
.btn-block-yellow {
@apply btn-yellow !block text-center;
}
.btn-red {
@apply btn border-none bg-red text-white hover:bg-red-dark;
}
.btn-block-red {
@apply btn-red !block text-center;
}
.workshop-card:nth-child(6n + 1) .btn-block {
@apply bg-pink hover:bg-pink-dark text-white border-none;
}
.workshop-card:nth-child(6n + 2) .btn-block {
@apply bg-orange hover:bg-orange-dark text-white border-none;
}
.workshop-card:nth-child(6n + 3) .btn-block {
@apply bg-yellow hover:bg-yellow-dark text-black border-none;
}
.workshop-card:nth-child(6n + 4) .btn-block {
@apply bg-green hover:bg-green-dark text-white border-none;
}
.workshop-card:nth-child(6n + 5) .btn-block {
@apply bg-blue hover:bg-blue-dark text-white border-none;
}
.workshop-card:nth-child(6n + 6) .btn-block {
@apply bg-red hover:bg-red-dark text-white border-none;
}
.admin-card {
@apply flex w-60 flex-col items-center border;
}
.action-column {
@apply flex gap-4 justify-center;
}

2
resources/js/app.ts Normal file
View File

@@ -0,0 +1,2 @@
import "./bootstrap";
import "./stemmech";

29
resources/js/bootstrap.ts Normal file
View File

@@ -0,0 +1,29 @@
import axios, { AxiosStatic } from "axios";
import stemmech, { StemmechStatic } from "./stemmech";
// Attach axios to the window object
declare global {
interface Window {
axios: AxiosStatic;
stemmech: StemmechStatic;
SVGInject: any;
}
}
window.axios = axios;
window.stemmech = stemmech;
// Set a default header for Axios requests
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
// Setup window ready
window.stemmech.ready(() => {
setTimeout(function () {
window.stemmech.cleanupBackLinks();
window.stemmech.inputErrorListener();
window.stemmech.formSubmitListener();
window.stemmech.formChangeListener();
}, 1);
window.SVGInject(document.querySelectorAll("img.injectable"));
});

302
resources/js/stemmech.ts Normal file
View File

@@ -0,0 +1,302 @@
export interface StemmechStatic {
hasUnsavedChanges: number;
unsavedChangesMessageStack: string[];
ready(callback: () => void): void;
getAllSiblings(
elem: HTMLElement,
filter?: (elem: HTMLElement) => boolean
): HTMLElement[];
getQueryParam(param: string, defaultValue: string | null): string | null;
cleanupBackLinks(): void;
inputErrorListener(): void;
formSubmitListener(): void;
formChangeListener(): void;
formBusy(message: string): void;
formIdle(popMessage: boolean): void;
}
const stemmech: StemmechStatic = {
hasUnsavedChanges: 0,
unsavedChangesMessageStack: ['You have unsaved changes. Are you sure you want to leave this page?'],
/**
* Executes the provided callback function when the DOM is fully loaded.
*
* @param {function} callback - The function to be executed when the DOM is ready.
*/
ready: function (callback) {
document.addEventListener("DOMContentLoaded", function () {
if (typeof callback === "function") {
callback();
}
});
},
/**
* Get all siblings of an element.
*
* @param {HTMLElement} elem - The element to find siblings for.
* @param {function} [filter] - A filter function to apply to the siblings.
* @returns {HTMLElement[]} An array of sibling elements.
*/
getAllSiblings: function (elem, filter) {
const sibs: HTMLElement[] = [];
elem = elem.parentNode?.firstChild as HTMLElement;
do {
if (elem?.nodeType === 3) continue;
if (!filter || (filter && filter(elem))) sibs.push(elem);
} while ((elem = elem.nextSibling as HTMLElement));
return sibs;
},
/**
* Get a query parameter from the current URL.
*
* @param {string} param - The query parameter to retrieve.
* @param {*} [defaultValue=null] - The default value if the parameter is not found.
* @returns {*} The value of the query parameter or the default value.
*/
getQueryParam: function (param, defaultValue = null) {
const urlSearchParams = new URLSearchParams(window.location.search);
const paramValue = urlSearchParams.get(param);
return paramValue !== null ? paramValue : defaultValue;
},
/**
* Cleans up back links in the document by replacing links with "javascript:history.back()" href attributes
* with the actual document.referrer value.
*/
cleanupBackLinks: function () {
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
if (links[i].getAttribute("href") === "javascript:history.back()") {
links[i].setAttribute("href", document.referrer);
}
}
},
/**
* Listens for input events on input elements with error-related siblings and removes the error
* siblings when input occurs.
*/
inputErrorListener: function () {
function handleRemoveErrorSiblings(event: Event) {
const element = event.currentTarget as HTMLInputElement;
const siblings = window.stemmech.getAllSiblings(element, (e) => {
return (
e.nodeName.toUpperCase() === "P" &&
e.classList.contains("error")
);
});
siblings.forEach((item) => {
if (item.parentNode) item.parentNode.removeChild(item);
});
element.removeEventListener("input", handleRemoveErrorSiblings);
}
// Attach event listener only to those inputs that have a following p.error sibling
document.querySelectorAll("input").forEach(function (input) {
const hasErrorSiblings = stemmech.getAllSiblings(input, (e) => {
return (
e.nodeName.toUpperCase() === "P" &&
e.classList.contains("error")
);
});
if (hasErrorSiblings.length > 0) {
input.addEventListener("input", handleRemoveErrorSiblings);
}
});
},
/**
* Handle the form submission by disabling input elements and showing a spinner.
*/
formSubmitListener: function () {
function handleFormSubmit(event: Event) {
const element = event.currentTarget as HTMLElement;
// Find the submit button in the form
var submitButtons = element.querySelectorAll(
'input[type="submit"], button[type="submit"]'
);
submitButtons.forEach((button) => {
var style = window.getComputedStyle(button);
(button as HTMLElement).style.width = style.width;
(button as HTMLElement).style.height = style.height;
// Change the HTML of the submit button
button.innerHTML =
'<i class="fa-solid fa-spinner fa-spin-pulse"></i>';
});
element
.querySelectorAll('input:not([type="submit"]), textarea')
.forEach(function (item) {
(item as HTMLInputElement).readOnly = true;
});
element
.querySelectorAll('input[type="submit"], button')
.forEach(function (item) {
(item as HTMLInputElement).disabled = true;
});
}
var form = document.querySelector("form");
if (form) {
form.addEventListener("submit", handleFormSubmit);
}
},
formChangeListener: function() {
const forms = document.querySelectorAll('form');
if (forms.length > 0) {
forms.forEach(form => {
form.addEventListener('input', () => {
this.hasUnsavedChanges++;
}, { once: true });
});
window.addEventListener('beforeunload', (event) => {
if (this.hasUnsavedChanges) {
event.preventDefault();
event.returnValue = this.unsavedChangesMessageStack[this.unsavedChangesMessageStack.length - 1];
return event.returnValue;
}
});
}
},
formBusy: function (message: string = "") {
this.hasUnsavedChanges++;
if (message != "") {
this.unsavedChangesMessageStack.push(message);
}
},
formIdle: function (popMessage: boolean = false) {
this.hasUnsavedChanges--;
if (popMessage) {
this.unsavedChangesMessageStack.pop();
}
},
function uploadFilesWithFeedback(files: FileList, url: string, formElement: HTMLFormElement, containerElement: HTMLElement, allowedExtensions: string[]): Promise<void[]> {
// Disable all submit buttons in the form
const submitButtons = formElement.querySelectorAll('input[type="submit"], button[type="submit"]');
submitButtons.forEach((button) => {
button.disabled = true;
});
// Create an array to store promises for each file upload
const uploadPromises: Promise<void>[] = [];
// Iterate through the files in the FileList
for (let i = 0; i < files.length; i++) {
const file = files[i];
// Check if the file has an allowed extension
const fileExtension = file.name.split('.').pop()?.toLowerCase();
if (!fileExtension || !allowedExtensions.includes(fileExtension)) {
// Skip this file if the extension is not allowed
console.warn(`Skipping file "${file.name}" due to an invalid extension.`);
continue;
}
// Create a promise for each file upload
const uploadPromise = new Promise<void>((resolve, reject) => {
// Create a FormData object to send the file
const formData = new FormData();
formData.append('file', file);
// Create a new DIV element for feedback
const feedbackDiv = document.createElement('div');
feedbackDiv.classList.add('upload-feedback');
// Set the background image of the feedback DIV
const backgroundUrl = `/public/file_icons/${fileExtension}.png`;
feedbackDiv.style.backgroundImage = `url(${backgroundUrl}), url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="lightgray"/></svg>')`;
// Create a spinner icon
const spinnerIcon = document.createElement('i');
spinnerIcon.classList.add('fas', 'fa-spinner', 'fa-spin');
// Append the spinner icon to the feedback DIV
feedbackDiv.appendChild(spinnerIcon);
// Append the feedback DIV to the container element
containerElement.appendChild(feedbackDiv);
// Perform the POST request
fetch(url, {
method: 'POST',
body: formData,
})
.then((response) => {
if (response.ok) {
// Remove spinner and add a tick icon for success
feedbackDiv.innerHTML = '<i class="fas fa-check"></i>';
resolve();
} else {
// Remove spinner and add an error icon for errors
feedbackDiv.innerHTML = '<i class="fas fa-exclamation-triangle"></i>';
console.error('File upload failed:', response.statusText);
reject(response.statusText);
}
})
.catch((error) => {
// Handle network errors
feedbackDiv.innerHTML = '<i class="fas fa-exclamation-circle"></i>';
console.error('File upload error:', error);
reject(error);
});
});
// Add the upload promise to the array
uploadPromises.push(uploadPromise);
}
// Return a promise that resolves when all uploads are complete (success or error)
return Promise.all(uploadPromises)
.finally(() => {
// Enable all submit buttons in the form when all uploads are complete
submitButtons.forEach((button) => {
button.disabled = false;
});
});
}
// Example usage:
const fileInput = document.getElementById('file-input') as HTMLInputElement;
const form = document.getElementById('my-form') as HTMLFormElement;
const container = document.getElementById('feedback-container');
const allowedExtensions = ['jpg', 'jpeg', 'png', 'gif']; // Replace with your allowed extensions
fileInput.addEventListener('change', () => {
const files = fileInput.files;
if (files.length > 0) {
uploadFilesWithFeedback(files, '/upload-url', form, container, allowedExtensions)
.then(() => {
console.log('All files uploaded successfully.');
})
.catch((error) => {
console.error('Error uploading files:', error);
});
}
});
};
export default stemmech;

View File

@@ -0,0 +1,40 @@
<x-layout>
<x-banner heading="Account" />
<div class="flex flex-wrap items-center justify-center gap-8 border">
<x-card href="/account/users/me" class="admin-card">
<i class="fa-regular fa-circle-user mb-4 text-4xl"></i>
<p class="text-lg">My Details</p>
</x-card>
<x-card href="/account/users" class="admin-card">
<i class="fa-solid fa-users mb-4 text-4xl"></i>
<p class="text-lg">Users</p>
</x-card>
<x-card href="/account/media" class="admin-card">
<i class="fa-solid fa-photo-film mb-4 text-4xl"></i>
<p class="text-lg">Media</p>
</x-card>
<x-card class="admin-card">
<i class="fa-regular fa-circle-user mb-4 text-4xl"></i>
<p class="text-lg">Posts</p>
</x-card>
<x-card class="admin-card">
<i class="fa-regular fa-circle-user mb-4 text-4xl"></i>
<p class="text-lg">Workshops</p>
</x-card>
<x-card class="admin-card">
<i class="fa-solid fa-file-invoice-dollar mb-4 text-4xl"></i>
<p class="text-lg">Quotes</p>
</x-card>
<x-card class="admin-card">
<i class="fa-solid fa-file-invoice-dollar mb-4 text-4xl"></i>
<p class="text-lg">Invoices</p>
</x-card>
</div>
</x-layout>

View File

@@ -0,0 +1,54 @@
@props(['users'])
<x-layout>
<x-banner heading="Users" back="account.index" />
<form method="post" action="/account/users">
<div class="table-layout">
<table cellspacing="0" cellpadding="0" class="w-full table-auto">
<thead>
<tr>
<th class="w-1">&nbsp;</th>
<th>Username</th>
<th>Email</th>
<th class="hidden md:table-cell">Verified</th>
<th class="hidden md:table-cell">Under 14</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@unless (count($users) == 0)
@foreach ($users as $user)
<tr>
<td><input type="checkbox"></td>
<td>{{ $user->username }}</td>
<td>{{ $user->email }}</td>
<td class="hidden md:table-cell">{{ $user->formattedEmailVerifiedAt() }}</td>
<td class="hidden text-center md:table-cell">{{ $user->is_under_14 ? 'Yes' : 'No' }}</td>
<td class="action-column">
<a href="#" title="Edit User"><i class="fa-solid fa-pen-to-square"></i></a>
<a href="#" title="Delete User"><i
class="fa-solid fa-trash hover:text-red"></i></a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="4">No users found</td>
</tr>
@endunless
</tbody>
</table>
</div>
</form>
<div>
<input type="submit" name="action" value="Edit">
<input type="submit" name="action" value="Delete">
</div>
<div class="mt-6">
{{ $users->links() }}
</div>
</x-layout>

View File

@@ -0,0 +1,35 @@
@props(['user'])
<x-layout>
<x-banner heading="User" back="account.users.index" />
<div class="mx-4">
<form class="mx-auto max-w-[40rem]" method="POST" action="/account/users/{{ $user->id }}">
<div class="floating-label">
<input type="text" name="username" value="{{ old('username', $user->username) }}" required />
<label for="username">Username</label>
</div>
@error('username')
<p class="error">{{ $message }}</p>
@enderror
<div class="floating-label">
<input type="email" name="email" value="{{ old('email', $user->email) }}" required />
<label for="email">Email</label>
</div>
@error('email')
<p class="error">{{ $message }}</p>
@enderror
<div>
<label><input type="checkbox" name="is_under_14" value="1"
@if (old('is_under_14', $user->is_under_14)) checked @endif />
Under 14 years</label>
</div>
<div class="flex justify-end">
<button class="btn-blue" type="submit">Save</button>
</div>
</form>
</div>
</x-layout>

View File

@@ -0,0 +1,23 @@
@props(['heading', 'back'])
@php
$backTitle = '';
if (isset($back)) {
$parts = explode('.', $back);
if (count($parts) > 1) {
$backTitle = ucwords($parts[count($parts) - 2]);
} else {
$backTitle = ucwords($parts[0]);
}
}
@endphp
<div class="px42 -mt-4 mb-8 bg-blue px-6 py-10 text-white">
<h1 class="text-4xl font-bold">{{ $heading }}</h1>
@if (isset($back))
<a href="{{ route($back) }}" class="mt-1 text-sm hover:text-inherit hover:underline"><i
class="fa-solid fa-chevron-left mr-2"></i>Back
to
{{ $backTitle }}</a>
@endif
</div>

View File

@@ -0,0 +1,10 @@
@if (isset($href))
<a href="{{ $href }}"
{{ $attributes->merge(['class' => 'bg-gray-50 border border-gray-300 rounded-lg p-6']) }}>
{{ $slot }}
</a>
@else
<div {{ $attributes->merge(['class' => 'bg-gray-50 border border-gray-300 rounded-lg p-6']) }}>
{{ $slot }}
</div>
@endif

View File

@@ -0,0 +1,25 @@
@if (session()->has('message'))
@php
$messageType = session('message-type', 'primary'); // Default to 'primary' if 'message_type' is not set
$messageClasses = [
'primary' => 'border-blue bg-blue-lighter text-blue-dark',
'success' => 'border-green bg-green-lighter text-green-dark',
'danger' => 'border-red bg-red-lighter text-red-dark',
'warning' => 'border-yellow bg-yellow-lighter text-yellow-dark',
];
@endphp
<div x-data="{ show: false }" x-init="$nextTick(() => {
show = true;
setTimeout(() => show = false, 7000)
})" x-show="show"
x-transition:enter="transition ease-out duration-300 transform"
x-transition:enter-start="opacity-0 -translate-y-4" x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-300 transform"
x-transition:leave-start="opacity-100 translate-y-0" x-transition:leave-end="opacity-0 -translate-y-4"
class="{{ $messageClasses[$messageType] }} fixed left-1/2 top-4 min-w-[15rem] max-w-[20rem] -translate-x-1/2 rounded border px-4 py-2 text-center shadow-lg">
<p class="text-sm">
{{ session('message') }}
</p>
</div>
@endif

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="images/favicon.ico" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"
integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.13.3/cdn.min.js"
integrity="sha512-AB2vAMVrtmmI+2BwSMqB+y1qGPNJovUOCp4w27S9pvX8yXPQNbBO4kuM952+LlOpng9VeWPb86b5N32bkvXRvQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
<script src="/scripts/svg-inject.min.js"></script>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap"
as="style" onload="this.onload=null;this.rel='stylesheet'">
@vite(['resources/css/app.css', 'resources/js/app.ts'])
<title>STEMMechanics</title>
</head>
<body class="flex flex-col bg-gray-200">
@include('partials.nav')
<main {{ $attributes->merge(['class' => 'grow']) }}>
{{ $slot }}
</main>
@include('partials.footer')
<x-flash-message />
@stack('scripts')
</body>
</html>

View File

@@ -0,0 +1,10 @@
@props(['workshop'])
<x-card class="workshop-card grow">
<div class="flex h-full flex-col">
<h3 class="mb-4 text-2xl">{{ $workshop->title }}</h3>
<p class="mb-4 grow">{{ Str::limit(strip_tags($workshop->content), 200, '...') }}</p>
<a href="/" class="btn btn-block">View</a>
</div>
</x-card>

View File

@@ -0,0 +1,18 @@
Welcome {{ $user?->username }},
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.
Enter the following URL in your browser:
https://www.stemmechanics.com.au/verify
and when asked, use the confirm code: {{ $code }}
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

@@ -0,0 +1,114 @@
<!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=Poppins:ital,wght@0,400;0,700;1,400;1,700&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=Poppins:ital,wght@0,400;0,700;1,400;1,700&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: Poppins, 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('images') . '/logo.svg') }}"
width="400" height="62" />
</a>
</td>
</tr>
<tr>
<td>
<h2>Welcome {{ $user?->username }},</h2>
</td>
</tr>
<tr>
<td>
We've heard you would like to try out our workshops and courses!
</td>
</tr>
<tr>
<td>
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. Click this link <a
href="https://www.stemmechanics.com.au/verify?code={{ $code }}">stemmechanics.com.au/verify</a>
and if you are asked, use the confirm code:
</td>
</tr>
<tr>
<td align="center"
style="
font-size: 200%;
text-align: center;
padding-top: 2rem;
padding-bottom: 2rem;
letter-spacing: 0.5rem;
">
<strong>{{ $code }}</strong>
</td>
</tr>
<tr>
<td style="padding-bottom: 2rem">
But if you didn't ask to reset your password, you can delete
this email and your password will remain the same.
</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

@@ -0,0 +1,18 @@
@props(['workshops'])
<x-layout>
@include('partials.hero')
<h2>Upcoming Workshops</h2>
<div class="mx-4 justify-start gap-4 space-y-4 sm:grid sm:grid-cols-2 sm:space-y-0">
@unless (count($workshops) == 0)
@foreach ($workshops as $workshop)
<x-workshop-card :workshop="$workshop" />
@endforeach
@else
<p>No workshops found</p>
@endunless
</div>
</x-layout>

View File

@@ -0,0 +1,55 @@
@props(['resend'])
<p>
Enter the verification code you received</p>
<div class="floating-label mb-6 mt-12">
<input type="text" class="w-full rounded border border-gray-200 p-2" name="code" value="{{ old('code') }}"
required autofocus />
<label for="code" class="mx-1 mb-1 inline-block text-sm text-gray-800">Verification code</label>
@error('code')
<p class="error">{{ $message }}</p>
@enderror
</div>
@if (isset($resend))
<div class="flex items-center justify-between">
<div>
<p id="resend-link" class="hidden text-xs text-gray-600">Didn't receive the code? <a href="?resend=1"
class="text-blue transition hover:text-blue-dark">
Resend Email
</a>
</p>
</div>
<div>
<button type="submit" class="rounded bg-green px-8 py-2 text-white transition hover:bg-green-dark">
Verify Code
</button>
</div>
@else
<div class="flex items-center justify-end">
<button type="submit" class="btn-green">
Verify Code
</button>
</div>
@endif
@push('scripts')
<script type="module">
window.stemmech.ready(() => {
const code = stemmech.getQueryParam('code');
const resend = document.getElementById('resend-link');
const codeInput = document.getElementsByName('code')[0];
if (code && codeInput && codeInput.value.trim() === '') {
codeInput.value = code;
var form = document.querySelector("form");
if (form) form.submit();
} else if (resend) {
window.setTimeout(() => {
resend.classList.remove('hidden');
}, 30000);
}
});
</script>
@endpush

View File

@@ -0,0 +1,43 @@
<footer class="mt-16 bg-gray-800 text-gray-400">
<section class="flex gap-4 border-b border-gray-900 p-8">
<div class="basis-1/2 self-center pr-8 text-xs">
<p>STEMMechanics Australia acknowledges the Traditional Owners of Country throughout Australia and the
continuing connection to land, cultures and communities. We pay our respect to Aboriginal and Torres
Strait Islander cultures; and to Elders both past, present and emerging.</p>
</div>
<div class="basis-1/4">
<h3 class="mb-2 font-bold">Community</h3>
<ul class="flex flex-col gap-1 text-sm">
<li class="flex max-w-[7rem] justify-between text-2xl">
<a href="/community"><i class="fa-brands fa-github"></i></a>
<a href="/community"><i class="fa-brands fa-discord"></i></a>
<a href="/community"><img src="/images/minecraft-icon.png" class="h-7 w-7"></a>
</li>
<li class="flex max-w-[7rem] justify-between text-2xl">
<a href="/community"><i class="fa-brands fa-facebook"></i></a>
<a href="/community"><i class="fa-brands fa-twitter"></i></a>
<a href="/community"><i class="fa-brands fa-youtube"></i></a>
</li>
</ul>
</div>
<div class="basis-1/4">
<h3 class="mb-2 font-bold">STEMMechanics</h3>
<ul class="flex flex-col gap-1 text-sm">
<li><a href="/">Contact Us</a></li>
<li><a href="/">Host a Workshop</a></li>
<li><a href="/">Code of Conduct</a></li>
<li><a href="/">Terms & Conditions</a></li>
<li><a href="/">Privacy Policy</a></li>
</ul>
</div>
</section>
<section class="flex items-center justify-between p-8">
<div>
<a href="/"><img class="injectable w-40 text-white" src="{{ asset('images/logo.svg') }}"
alt="" class="logo" /></a>
</div>
<div class="text-xs">
Made with ❤️ &copy; 2023 STEMMechanics
</div>
</section>
</footer>

View File

@@ -0,0 +1,9 @@
<section
class="mx-4 mb-4 flex flex-col items-center justify-center rounded bg-cover bg-center bg-no-repeat py-20 text-white"
style="background-image: linear-gradient(to right,rgba(0,0,0,.7),rgba(0,0,0,.2)),url('/images/hero.webp')">
<h1 class="py-4 text-4xl font-bold">Join the fun!</h1>
<p class="max-w-2xl px-4 py-2.5">To keep up with our ever-changing world, it's important to encourage and support a
new generation of curious minds who love science, engineering, art, and leadership.</p>
<p class="max-w-2xl px-4 py-2.5">Our fun and exciting workshops can unlock countless opportunities for new ideas and
improvements, giving kids the skills and tools they need to solve any problem that comes their way.</p>
</section>

View File

@@ -0,0 +1,77 @@
<nav class="mb-4 flex items-center justify-between bg-white shadow-sm">
<a href="/"><img class="injectable w-60 px-4 py-3" src="{{ asset('images/logo.svg') }}" alt=""
class="logo" /></a>
<ul class="text mr-6 flex">
<li class="b-1 z-30 flex justify-center">
<div x-data="{
open: false,
toggle() {
if (this.open) {
return this.close()
}
this.$refs.button.focus()
this.open = true
},
close(focusAfter) {
if (!this.open) return
this.open = false
focusAfter && focusAfter.focus()
}
}" x-on:keydown.escape.prevent.stop="close($refs.button)"
x-on:focusin.window="! $refs.panel.contains($event.target) && close()" x-id="['dropdown-button']"
class="relative">
<button x-ref="button" x-on:click="toggle()" :aria-expanded="open"
:aria-controls="$id('dropdown-button')" type="button"
class="flex items-center gap-2 rounded-md bg-blue px-4 py-2 text-white">
Menu
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
<div x-ref="panel" x-show="open" x-transition.origin.top.left x-on:click.outside="close($refs.button)"
:id="$id('dropdown-button')" style="display: none;"
class="absolute right-0 mt-2 whitespace-nowrap rounded-md border bg-white p-2 shadow-md">
<a href="/workshops"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-solid fa-paintbrush"></i> Workshops
</a>
<a href="/workshops"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-regular fa-newspaper"></i> Blog
</a>
<hr class="my-2 border-gray-200" />
@auth
<a href="{{ route('account.index') }}"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-solid fa-toolbox"></i>My Account
</a>
<a href="/logout"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-solid fa-right-from-bracket"></i> Log out
</a>
@else
<a href="/register"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-solid fa-user-plus"></i> Register
</a>
<a href="/login"
class="text-md flex w-full items-center gap-2 rounded-md px-4 py-2.5 transition hover:bg-blue hover:text-white">
<i class="fa-solid fa-right-to-bracket"></i> Log in
</a>
@endif
</div>
</div>
</li>
</ul>
</nav>

View File

@@ -0,0 +1,15 @@
<form action="/">
<div class="relative m-4 rounded-lg border border-gray-400">
<div class="absolute left-4 top-4">
<i class="fa fa-search z-20 text-gray-400 hover:text-gray-500"></i>
</div>
<input type="text" name="search" class="z-0 h-14 w-full rounded-lg pl-11 pr-20 focus:shadow focus:outline-none"
placeholder="Search Workshops" />
<div class="absolute right-2 top-2">
<button type="submit"
class="h-10 w-20 rounded-lg bg-green text-sm text-white transition hover:bg-green-dark">
Search
</button>
</div>
</div>
</form>

View File

@@ -0,0 +1,43 @@
<x-layout>
<x-card class="relative z-40 mx-auto mt-12 max-w-lg shadow-lg">
<header>
<h2 class="-m-6 mb-10 rounded-t-lg bg-orange px-6 py-4 text-xl text-white">Log in to STEMMechanics</h2>
</header>
<form method="POST" action="/login">
@csrf
<div class="floating-label my-8">
<input type="text" class="w-full rounded border border-gray-200 p-2" name="username"
value="{{ old('username') }}" required />
<label for="username" class="mb-1 inline-block text-sm text-gray-800">Username</label>
@error('username')
<p class="error">{{ $message }}</p>
@enderror
</div>
<div class="floating-label my-8">
<input type="password" class="w-full rounded border border-gray-200 p-2" name="password"
value="{{ old('password') }}" placeholder="Password" />
<label for="password">Password</label>
@error('password')
<p class="error">{{ $message }}</p>
@enderror
<p class="mt-1 px-1 text-xs"><a href="/forgot-password"
class="text-blue transition hover:text-blue-dark">
Forgot Password</a></p>
</div>
<div class="flex items-end justify-between">
<p class="text-xs text-gray-600">Need an account?
<a href="/register" class="text-blue transition hover:text-blue-dark">
Register</a>
</p>
<button type="submit" class="rounded bg-orange px-8 py-2 text-white transition hover:bg-orange-dark">
Login
</button>
</div>
</form>
</x-card>
</x-layout>

View File

@@ -0,0 +1,104 @@
<x-layout>
<x-card class="relative mx-auto mt-12 max-w-lg shadow-lg">
<header class="relative">
<h2 class="-m-6 mb-6 rounded-t-lg bg-green px-6 py-4 text-xl text-white">Sign up to STEMMechanics</h2>
<a href="?reset=1" class="text-white transition hover:text-red" title="Restart registration"><i
class="fa-solid fa-xmark absolute right-0 top-0 translate-y-1/2 text-xl"></i></a>
</header>
<form class="m-0" method="POST" action="/register" x-data="{ age: '' }">
<input type="hidden" name="form_step" value="{{ $form->currentStep() }}">
@csrf
@switch($form->currentStep())
@case(1)
<div class="floating-label mb-6 mt-12">
<input type="text" class="w-full rounded border border-gray-200 p-2" name="username"
value="{{ $form->getValue('username') }}" required autofocus />
<label for="username" class="mx-1 mb-1 inline-block text-sm text-gray-800">Choose a username</label>
@error('username')
<p class="error">{{ $errors->first('username') }}</p>
@enderror
</div>
<div class="floating-label my-8">
<input type="password" class="w-full rounded border border-gray-200 p-2" name="password"
value="{{ $form->getValue('password') }}" placeholder="Password" />
<label for="password">Choose a password</label>
@error('password')
<p class="error">{{ $errors->first('password') }}</p>
@enderror
<p class="mt-1 px-1 text-xs text-gray-400">Required to be at least 8 characters and include a
number</p>
</div>
@break
@case(2)
<p>Are you over or under 14 years old?</p>
<input type="hidden" name="age" x-model="age">
<button x-on:click="age = $event.target.value" type="submit" value="under" class="btn mt-8 w-full"
tabindex="1">I
am under 14</button>
<button x-on:click="age = $event.target.value" x-data="" value="over" type="submit"
class="btn my-6 w-full" tabindex="2">I am 14 or older</button>
@break
@case(3)
@if ($form->getValue('age') == 'over')
<p>Please enter your email address so we can verify your account</p>
@else
<p>Please find a parent or guardian's email address, and we can verify your account</p>
@endif
<div class="floating-label my-6">
<input type="email" class="w-full rounded border border-gray-200 p-2" name="email"
value="{{ old('email') }}" required autocomplete="off" spellcheck="false" autocorrect="off"
autofocus />
@if ($form->getValue('age') == 'over')
<label for="email" class="mb-1 inline-block text-sm text-gray-800">Your email</label>
@else
<label for="email" class="mb-1 inline-block text-sm text-gray-800">Parent or guardian's
email</label>
@endif
@error('email')
<p class="error">{{ $message }}</p>
@enderror
</div>
@break
@case(4)
@include('partials.email-verify', ['resend' => true])
@break
@endswitch
@if ($form->currentStep() < 4)
<div class="flex items-center justify-between">
@if ($form->currentStep() == 1)
<small class="text-xs text-gray-600">Already have an account? <a href="{{ route('login') }}"
class="text-blue transition hover:text-blue-dark">Login</a></small>
@elseif ($form->currentStep() < 4)
<a href="{{ route('register', ['form_step' => $form->currentStep() - 1]) }}"
class="text-blue transition hover:text-blue-dark">
<i class="fa-solid fa-angle-left mr-2"></i>Back</a>
@endif
@if ($form->currentStep() == 1)
<button type="submit"
class="rounded bg-green px-8 py-2 text-white transition hover:bg-green-dark">
Next<i class="fa-solid fa-angle-right ml-2"></i>
</button>
@elseif ($form->currentStep() == 3)
<button type="submit"
class="rounded bg-green px-8 py-2 text-white transition hover:bg-green-dark">
Verify
</button>
@endif
</div>
@endif
{{ $form->getValue('error') }}
</form>
</x-card>
</x-layout>

View File

@@ -0,0 +1,13 @@
<x-layout>
<x-card class="relative mx-auto mt-12 max-w-lg shadow-lg">
<header>
<h2 class="-m-6 mb-6 rounded-t-lg bg-green px-6 py-4 text-xl text-white">Sign up to STEMMechanics</h2>
</header>
<form method="POST" action="/verify" id="verification-form">
@csrf
@include('partials.email-verify')
</form>
</x-card>
</x-layout>

View File

@@ -0,0 +1,46 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,88 @@
@if ($paginator->hasPages())
<nav class="d-flex justify-items-center justify-content-between">
<div class="d-flex justify-content-between flex-fill d-sm-none">
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.previous')</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
</li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.next')</span>
</li>
@endif
</ul>
</div>
<div class="d-none flex-sm-fill d-sm-flex align-items-sm-center justify-content-sm-between">
<div>
<p class="small text-muted">
{!! __('Showing') !!}
<span class="fw-semibold">{{ $paginator->firstItem() }}</span>
{!! __('to') !!}
<span class="fw-semibold">{{ $paginator->lastItem() }}</span>
{!! __('of') !!}
<span class="fw-semibold">{{ $paginator->total() }}</span>
{!! __('results') !!}
</p>
</div>
<div>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
@endif
</ul>
</div>
</div>
</nav>
@endif

View File

@@ -0,0 +1,46 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
<span aria-hidden="true">&lsaquo;</span>
</li>
@else
<li>
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li class="disabled" aria-disabled="true"><span>{{ $element }}</span></li>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="active" aria-current="page"><span>{{ $page }}</span></li>
@else
<li><a href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li>
<a href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
</li>
@else
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
<span aria-hidden="true">&rsaquo;</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,36 @@
@if ($paginator->hasPages())
<div class="ui pagination menu" role="navigation">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
@else
<a class="icon item" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<a class="icon item disabled" aria-disabled="true">{{ $element }}</a>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<a class="item active" href="{{ $url }}" aria-current="page">{{ $page }}</a>
@else
<a class="item" href="{{ $url }}">{{ $page }}</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a class="icon item" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
@else
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
@endif
</div>
@endif

View File

@@ -0,0 +1,27 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.previous')</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
</li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.next')</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,29 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="Pagination Navigation">
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">{!! __('pagination.previous') !!}</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">
{!! __('pagination.previous') !!}
</a>
</li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">{!! __('pagination.next') !!}</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">{!! __('pagination.next') !!}</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,19 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="disabled" aria-disabled="true"><span>@lang('pagination.previous')</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a></li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a></li>
@else
<li class="disabled" aria-disabled="true"><span>@lang('pagination.next')</span></li>
@endif
</ul>
</nav>
@endif

View File

@@ -0,0 +1,29 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="Pagination Navigation" class="flex justify-between">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span
class="relative inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-500">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
class="focus:border-blue-300 relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-700 ring-gray-300 transition duration-150 ease-in-out hover:text-gray-500 focus:outline-none focus:ring active:bg-gray-100 active:text-gray-700">
{!! __('pagination.previous') !!}
</a>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
class="focus:border-blue-300 relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-700 ring-gray-300 transition duration-150 ease-in-out hover:text-gray-500 focus:outline-none focus:ring active:bg-gray-100 active:text-gray-700">
{!! __('pagination.next') !!}
</a>
@else
<span
class="relative inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-500">
{!! __('pagination.next') !!}
</span>
@endif
</nav>
@endif

View File

@@ -0,0 +1,46 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="Pagination Navigation" class="flex items-center justify-between">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span
class="relative inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-500">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
class="focus:border-blue-300 relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-700 ring-gray-300 transition duration-150 ease-in-out hover:text-gray-500 focus:outline-none focus:ring active:bg-gray-100 active:text-gray-700">
{!! __('pagination.previous') !!}
</a>
@endif
<div>
<p class="text-xs leading-5 text-gray-500">
{!! __('Showing') !!}
@if ($paginator->firstItem())
<span class="font-medium">{{ $paginator->firstItem() }}</span>
{!! __('to') !!}
<span class="font-medium">{{ $paginator->lastItem() }}</span>
@else
{{ $paginator->count() }}
@endif
{!! __('of') !!}
<span class="font-medium">{{ $paginator->total() }}</span>
{!! __('results') !!}
</p>
</div>
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
class="focus:border-blue-300 relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-700 ring-gray-300 transition duration-150 ease-in-out hover:text-gray-500 focus:outline-none focus:ring active:bg-gray-100 active:text-gray-700">
{!! __('pagination.next') !!}
</a>
@else
<span
class="relative inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-500">
{!! __('pagination.next') !!}
</span>
@endif
</nav>
@endif

View File

@@ -0,0 +1,20 @@
<x-layout>
@include('partials.search')
<div class="mx-4 justify-start gap-4 space-y-4 sm:grid sm:grid-cols-2 sm:space-y-0">
@unless (count($workshops) == 0)
@foreach ($workshops as $workshop)
<x-workshop-card :workshop="$workshop" />
@endforeach
@else
<p>No workshops found</p>
@endunless
</div>
<div class="mt-6 p-4">
{{ $workshops->links() }}
</div>
</x-layout>