removed blade and htmx

This commit is contained in:
2026-05-06 23:16:09 +03:00
parent dff3748623
commit 98efb76f7b
31 changed files with 235 additions and 465 deletions
-51
View File
@@ -1,51 +0,0 @@
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from "axios";
import {loadHtmxFormsBehaviour} from "./htmx-form.js";
loadHtmxFormsBehaviour();
window.axios = axios;
export const axiosInstance = axios;
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
window.axios.interceptors.request.use(
function (config) {
let list;
list = document.querySelectorAll(".btn-spinner");
for (let i = 0; i < list.length; ++i) {
list[i].classList.add("spinner-on");
list[i].disabled = true;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
window.axios.interceptors.response.use(
function (response) {
let list;
list = document.querySelectorAll(".btn-spinner");
for (let i = 0; i < list.length; ++i) {
list[i].classList.remove("spinner-on");
list[i].disabled = false;
}
return response;
},
function (error) {
let list;
list = document.querySelectorAll(".btn-spinner");
for (let i = 0; i < list.length; ++i) {
list[i].classList.remove("spinner-on");
list[i].disabled = false;
}
return Promise.reject(error);
}
);
+60 -29
View File
@@ -1,52 +1,83 @@
import {formatDistanceToNow, parseJSON, format, parse} from "date-fns";
import { formatDistanceToNow, parseJSON, format, parse } from "date-fns";
export function friendlyDate(date) {
return formatDistanceToNow(parseJSON(date), {addSuffix: true});
return formatDistanceToNow(parseJSON(date), { addSuffix: true });
}
export function readableDate(date) {
if(!date){
return "";
}
return format(parseJSON(date), "dd MMM yyyy");
if (!date) {
return "";
}
return format(parseJSON(date), "dd MMM yyyy");
}
export function readableDatetime(date) {
if(!date){
return "";
}
if (!date) {
return "";
}
return format(parseJSON(date), "dd MMM yyyy HH:mm");
return format(parseJSON(date), "dd MMM yyyy HH:mm");
}
export function stripHtml(html = "") {
let tmp = document.createElement("div");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
let tmp = document.createElement("div");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}
export function randomId(length = 10) {
return Math.random().toString(36).substring(2, length + 2);
return Math.random()
.toString(36)
.substring(2, length + 2);
}
export function clickOutside(node) {
const handleClick = event => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
node.dispatchEvent(
new CustomEvent('click_outside', node)
)
}
const handleClick = (event) => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
node.dispatchEvent(new CustomEvent("click_outside", node));
}
};
document.addEventListener('click', handleClick, true);
document.addEventListener("click", handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
}
return {
destroy() {
document.removeEventListener("click", handleClick, true);
},
};
}
export function apiFetch(url, options = {}) {
return fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
...options.headers,
},
});
}
export function apiPost(url, body, options = {}) {
return fetch(url, {
...options,
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
...options.headers,
},
});
}
export function apiGet(url, options = {}) {
return fetch(url, {
...options,
method: "GET",
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
...options.headers,
},
}).then((r) => r.json());
}
+28 -34
View File
@@ -1,53 +1,47 @@
import {axiosInstance} from "./bootstrap";
import "../sass/app.scss";
import Account from "./svelte/Account.svelte";
import Channel from "./svelte/Channel.svelte";
import Mustache from "mustache";
import 'htmx.org';
Mustache.escape = function (value) {
return value;
};
// import Mustache from "mustache";
// Mustache.escape = function (value) {
// return value;
// };
// Define all components
const entryComponents = {
account: Account,
channel: Channel,
account: Account,
channel: Channel,
};
let loadedComponents = [];
let loadSvelte = function () {
loadedComponents.map((comp) => comp.$destroy());
loadedComponents = [];
loadedComponents.map((comp) => comp.$destroy());
loadedComponents = [];
const elements = document.body.querySelectorAll(".lucent-component");
if (elements.length === 0) {
return;
const elements = document.body.querySelectorAll(".lucent-component");
if (elements.length === 0) {
return;
}
const loadElement = function (element) {
const componentId = element.attributes["data-layout"].value;
const [_, component] = Object.entries(entryComponents).find(
([key, _]) => componentId === key,
);
if (!component) {
return [];
}
const loadElement = function (element) {
const componentId = element.attributes["data-layout"].value;
const [_, component] = Object.entries(entryComponents).find(
([key, _]) => componentId === key
);
if (!component) {
return [];
}
const jsonData = document.getElementById(
"json-" + componentId
).innerHTML;
const props = JSON.parse(jsonData);
props.axios = axiosInstance;
const compOptions = {
target: element,
props: props,
};
loadedComponents = [...loadedComponents, new component(compOptions)];
const jsonData = document.getElementById("json-" + componentId).innerHTML;
const props = JSON.parse(jsonData);
const compOptions = {
target: element,
props: props,
};
Array.from(elements).map(loadElement);
loadedComponents = [...loadedComponents, new component(compOptions)];
};
Array.from(elements).map(loadElement);
};
// document.addEventListener("turbo:load", loadSvelte);
+4 -7
View File
@@ -4,7 +4,7 @@
import Verify from "./account/Verify.svelte";
import Profile from "./account/Profile.svelte";
import SetupIndex from "./setup/Index.svelte";
import {setContext} from "svelte";
import { setContext } from "svelte";
const components = {
register: Register,
@@ -23,10 +23,7 @@
setContext("channel", channel);
setContext("user", user);
</script>
<div style="text-align: center;background: var(--p20);padding: 20px;color: var(--p90)">
<h1><a class="text-decoration-none" href="{channel.lucentUrl}">{channel.name ?? "Lucent Setup"}</a></h1>
</div>
<div>
<svelte:component this={components[view]} {title} {...data}/>
</div>
<div>
<svelte:component this={components[view]} {channel} {title} {...data} />
</div>
+51 -37
View File
@@ -1,54 +1,68 @@
<script>
import {getContext} from "svelte";
import SpinnerButton from "../common/SpinnerButton.svelte";
import { getContext } from "svelte";
import { apiPost } from "../../helpers";
const channel = getContext("channel");
let email = "";
let message = "";
let submitted = false;
function login(e) {
e.preventDefault();
axios
.post(channel.lucentUrl + "/login", {
email: email,
apiPost(channel.lucentUrl + "/login", { email: email })
.then(() => {
submitted = true;
})
.then((response) => {
console.log(response)
message = "You will receive an email with a login link"
})
.catch((error) => {
});
.catch(() => {});
}
</script>
<div class="wrapper-tiny">
{#if message}
<div class="alert alert-info" role="alert">
{message}
</div>
{:else}
<form on:submit={login}>
<div class="mb-3">
<label for="emailaddress" class="form-label">Email address</label>
<input
type="email"
bind:value={email}
class="form-control"
id="emailaddress"
required
/>
<div class="scope-login">
<div class="bg-image"></div>
<div class="login-form">
{#if submitted}
<div class="alert alert-info" role="alert">
<p>
You will receive an email with a login link at <b>{email}</b
>.
</p>
<p>Check your spam folder</p>
</div>
{:else}
<div class="form">
<h2 class="mb-5">Enter Lucent</h2>
<form on:submit={login}>
<p>
Submit your email address and you will receive a <b
>login link</b
> to your email
</p>
<p>Don't forget to check your spam folder</p>
<div class="mt-5 mb-3">
<label for="emailaddress" class="form-label"
>Email address</label
>
<input
type="email"
bind:value={email}
class="form-control"
id="emailaddress"
required
/>
</div>
<div class="text-center mt-5 d-block">
<SpinnerButton label="Login"/>
<button class="bt bt-primary">
Send email
<img
alt="indicator"
id="indicator"
class="htmx-indicator"
src="/img/spinner.svg"
/>
</button>
</form>
</div>
</form>
{/if}
{/if}
</div>
</div>
+25 -26
View File
@@ -1,43 +1,42 @@
<script>
import {getContext} from "svelte";
import SpinnerButton from "../common/SpinnerButton.svelte";
import SuccessAlert from "../common/SuccessAlert.svelte";
import { getContext } from "svelte";
import { apiPost } from "../../helpers";
const channel = getContext("channel");
export let email;
export let token;
let successAlert;
function login(e) {
e.preventDefault();
axios
.post(channel.lucentUrl + "/verify", {
email: email,
token: token,
})
apiPost(channel.lucentUrl + "/verify", {
email: email,
token: token,
})
.then((response) => {
window.location = channel.lucentUrl;
})
.catch((error) => {
});
.catch((error) => {});
}
</script>
<SuccessAlert bind:this={successAlert}/>
<div class="wrapper-tiny">
<form on:submit={login}>
<div class="mb-3 text-center">
<h3>Login as {email}</h3>
<div class="scope-login">
<div class="bg-image"></div>
<div class="login-form">
<div class="form">
<h2 class="mb-5">Welcome to Lucent</h2>
<form on:submit={login}>
<button class="bt bt-primary">
Enter as {email}
<img
alt="indicator"
id="indicator"
class="htmx-indicator"
src="/img/spinner.svg"
/>
</button>
</form>
<div class="form-errors"></div>
</div>
<div class="text-center mt-5 d-block">
<SpinnerButton label="Enter"/>
</div>
</form>
</div>
</div>
+8 -8
View File
@@ -3,11 +3,11 @@
export let disabled = false;
</script>
<button type="submit" class="button secondary btn-spinner" {disabled}>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
{label}
</button>
<button type="submit" class="button secondary btn-spinner" {disabled}>
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
{label}
</button>
+6 -7
View File
@@ -1,18 +1,17 @@
<script>
import { getContext, onMount } from "svelte";
import RecordRow from "./RecordRow.svelte";
import { apiGet } from "../../helpers";
const channel = getContext("channel");
let records = [];
let graph = null;
let users = [];
onMount(() => {
axios
.get(channel.lucentUrl + "/home/records")
.then((response) => {
records = response.data.records;
graph = response.data.graph;
users = response.data.users;
apiGet(channel.lucentUrl + "/home/records")
.then((data) => {
records = data.records;
users = data.users;
})
.catch((error) => {
console.log(error);
@@ -27,7 +26,7 @@
<tbody>
{#each records as record (record.id)}
<tr>
<RecordRow {graph} {record} {users} />
<RecordRow {record} {users} />
</tr>
{/each}
</tbody>
-1
View File
@@ -7,7 +7,6 @@
const channel = getContext("channel");
export let users;
export let graph;
export let record;
let schema = channel.schemas.find((s) => s.name === record.schema);
let frieldlyUpdatedAt = formatDistanceToNow(parseJSON(record.updatedAt), {
+16 -8
View File
@@ -1,20 +1,28 @@
<script>
import Step from "./Step.svelte"
import Step from "./Step.svelte";
export let channel;
export let steps;
export let allSuccess = false;
console.log(steps);
</script>
<div class="wrapper-tiny">
<div
style="text-align: center;background: var(--p20);padding: 20px;color: var(--p90)"
>
<h1>
<a class="text-decoration-none" href={channel.lucentUrl}
>{channel.name ?? "Lucent Setup"}</a
>
</h1>
</div>
<div class="wrapper-tiny">
{#each steps as step}
<Step {step}></Step>
{/each}
<div style="text-align: center;margin-top: 30px;">
{#if allSuccess}
<a href="/lucent/register" class="bt">Create the first user</a>
{/if}
{#if allSuccess}
<a href="/lucent/register" class="bt">Create the first user</a>
{/if}
</div>
</div>
</div>