Compare commits
3 Commits
ca65d2986e
...
a33c1d8f5b
| Author | SHA1 | Date | |
|---|---|---|---|
| a33c1d8f5b | |||
| ae04d2b7b3 | |||
| 8faa93d976 |
@@ -15,6 +15,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@iconify/svelte": "^4.2.0",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -27,6 +27,9 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.18.0
|
||||
version: 9.23.0
|
||||
'@iconify/svelte':
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(svelte@5.25.3)
|
||||
'@sveltejs/adapter-auto':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@6.2.3(jiti@2.4.2)(lightningcss@1.29.2)))(svelte@5.25.3)(vite@6.2.3(jiti@2.4.2)(lightningcss@1.29.2)))
|
||||
@@ -322,6 +325,14 @@ packages:
|
||||
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@iconify/svelte@4.2.0':
|
||||
resolution: {integrity: sha512-fEl0T7SAPonK7xk6xUlRPDmFDZVDe2Z7ZstlqeDS/sS8ve2uyU+Qa8rTWbIqzZJlRvONkK5kVXiUf9nIc+6OOQ==}
|
||||
peerDependencies:
|
||||
svelte: '>4.0.0'
|
||||
|
||||
'@iconify/types@2.0.0':
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
|
||||
'@inlang/paraglide-js@2.0.4':
|
||||
resolution: {integrity: sha512-5p2Mia2PnwafJQtG6S2UqoHKhqUK7l0goMc5mI6AqQ2lEh14Fkl5uqYwUaI49s6Du4GX5Or1Wp+yBlOA74MKOQ==}
|
||||
hasBin: true
|
||||
@@ -1792,6 +1803,13 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.2': {}
|
||||
|
||||
'@iconify/svelte@4.2.0(svelte@5.25.3)':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
svelte: 5.25.3
|
||||
|
||||
'@iconify/types@2.0.0': {}
|
||||
|
||||
'@inlang/paraglide-js@2.0.4':
|
||||
dependencies:
|
||||
'@inlang/recommend-sherlock': 0.2.1
|
||||
|
||||
35
src/lib/FormInput.svelte
Normal file
35
src/lib/FormInput.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>">
|
||||
import { Field, Control, FieldErrors } from "formsnap";
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
|
||||
let { form, type, label, name }: {
|
||||
form: SuperForm<T>,
|
||||
type: string,
|
||||
label: string,
|
||||
name: FormPath<T>,
|
||||
} = $props();
|
||||
const formData = form.form;
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Field {form} {name}>
|
||||
<Control>
|
||||
{#snippet children({ props })}
|
||||
<label class="label floating-label w-full">
|
||||
<span>{label}</span>
|
||||
<input {type} class="input w-full aria-[invalid]:input-error" placeholder={label}
|
||||
bind:value={$formData[name]} {...props}
|
||||
/>
|
||||
</label>
|
||||
{/snippet}
|
||||
</Control>
|
||||
<FieldErrors>
|
||||
{#snippet children({ errors, errorProps })}
|
||||
{#each errors as err, idx (idx)}
|
||||
<p class="text-error" {...errorProps}>{err}</p>
|
||||
{/each}
|
||||
{/snippet}
|
||||
</FieldErrors>
|
||||
</Field>
|
||||
</div>
|
||||
49
src/lib/PwdFormInput.svelte
Normal file
49
src/lib/PwdFormInput.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>">
|
||||
import { Field, Control, FieldErrors } from "formsnap";
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
import { scale } from 'svelte/transition';
|
||||
import Icon from "@iconify/svelte";
|
||||
|
||||
let { form, label, name }: {
|
||||
form: SuperForm<T>,
|
||||
label: string,
|
||||
name: FormPath<T>,
|
||||
} = $props();
|
||||
const formData = form.form;
|
||||
|
||||
let show = $state(false);
|
||||
let type = $derived(show ? 'text' : 'password');
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Field {form} {name}>
|
||||
<Control>
|
||||
{#snippet children({ props })}
|
||||
<div class="flex w-full gap-1">
|
||||
<label class="label floating-label w-full">
|
||||
<span>{label}</span>
|
||||
<input {type} class="input w-full aria-[invalid]:input-error" placeholder={label}
|
||||
bind:value={$formData[name]} {...props}
|
||||
/>
|
||||
</label>
|
||||
<button class="btn btn-circle grid" onclick={() => show = !show} type="button">
|
||||
{#if show}
|
||||
<span transition:scale class="col-start-1 col-end-2 row-start-1 row-end-2"><Icon
|
||||
icon="mdi:eye-off-outline"/></span>
|
||||
{:else }
|
||||
<span transition:scale class="col-start-1 col-end-2 row-start-1 row-end-2"><Icon
|
||||
icon="mdi:eye-outline"/></span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Control>
|
||||
<FieldErrors>
|
||||
{#snippet children({ errors, errorProps })}
|
||||
{#each errors as err, idx (idx)}
|
||||
<p class="text-error" {...errorProps}>{err}</p>
|
||||
{/each}
|
||||
{/snippet}
|
||||
</FieldErrors>
|
||||
</Field>
|
||||
</div>
|
||||
22
src/lib/icons/ConnType2.svelte
Normal file
22
src/lib/icons/ConnType2.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg
|
||||
id="Calque_1"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 101 92"
|
||||
style="enable-background: new 0 0 101 92"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<path d="M15,40c0,5.5,4.5,10,10,10s10-4.5,10-10s-4.5-10-10-10S15,34.5,15,40z M20,40c0-2.8,2.2-5,5-5s5,2.2,5,5s-2.2,5-5,5
|
||||
S20,42.8,20,40z M27,62c0,5.5,4.5,10,10,10s10-4.5,10-10s-4.5-10-10-10S27,56.5,27,62z M32,62c0-2.8,2.2-5,5-5s5,2.2,5,5s-2.2,5-5,5
|
||||
S32,64.8,32,62z M53,62c0,5.5,4.5,10,10,10s10-4.5,10-10s-4.5-10-10-10S53,56.5,53,62z M58,62c0-2.8,2.2-5,5-5s5,2.2,5,5s-2.2,5-5,5
|
||||
S58,64.8,58,62z M40,40c0,5.5,4.5,10,10,10s10-4.5,10-10s-4.5-10-10-10S40,34.5,40,40z M45,40c0-2.8,2.2-5,5-5s5,2.2,5,5s-2.2,5-5,5
|
||||
S45,42.8,45,40z M65,40c0,5.5,4.5,10,10,10s10-4.5,10-10s-4.5-10-10-10S65,34.5,65,40z M70,40c0-2.8,2.2-5,5-5s5,2.2,5,5s-2.2,5-5,5
|
||||
S70,42.8,70,40z M55,23c0,4.4,3.6,8,8,8s8-3.6,8-8s-3.6-8-8-8S55,18.6,55,23z M60,23c0-1.7,1.3-3,3-3s3,1.3,3,3s-1.3,3-3,3
|
||||
S60,24.7,60,23z M30,23c0,4.4,3.6,8,8,8s8-3.6,8-8s-3.6-8-8-8S30,18.6,30,23z M35,23c0-1.7,1.3-3,3-3s3,1.3,3,3s-1.3,3-3,3
|
||||
S35,24.7,35,23z M0,41.5C0,69.4,22.6,92,50.5,92S101,69.4,101,41.5c0-12.8-5.3-24.1-12.7-33.4C82,0.3,70.8,0,70.8,0H30.7
|
||||
c-2.8,0-11.8,0.2-17.9,7.8C4.9,16.7,0,28.5,0,41.5z M9,42.4C9,32,13,22.5,19.5,15.3C24.4,9.2,31.7,9,34,9h32.5c0,0,9,0.2,14.2,6.5
|
||||
C86.7,23,91,32.1,91,42.4C91,64.8,72.6,83,50,83S9,64.8,9,42.4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
src/routes/+layout.ts
Normal file
13
src/routes/+layout.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async ({ fetch }) => {
|
||||
const user = await fetch('/api/public/1/auth/myself/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
return {
|
||||
user: user.ok ? await user.json() : null
|
||||
};
|
||||
};
|
||||
@@ -1,10 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import type { LayoutProps } from './$types';
|
||||
import ConnType2 from "$lib/icons/ConnType2.svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import { goto, invalidateAll } from "$app/navigation";
|
||||
|
||||
let { data, children }: LayoutProps = $props();
|
||||
const cc = $derived(data.chargecontroller);
|
||||
const user = $derived(data.user);
|
||||
const qrcode = $derived(page.params.qrcode);
|
||||
|
||||
let logouting = $state(false);
|
||||
|
||||
async function logout() {
|
||||
logouting = true;
|
||||
try {
|
||||
const response = await fetch("/api/public/1/auth/logout/", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
await invalidateAll();
|
||||
await goto(`/${qrcode}`);
|
||||
}
|
||||
} finally {
|
||||
logouting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-screen">
|
||||
@@ -15,21 +39,50 @@
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<li><p>QRCODE: {qrcode}</p></li>
|
||||
<li><p>
|
||||
{#if user}
|
||||
{user['username']}
|
||||
{:else}
|
||||
No User
|
||||
{/if}
|
||||
</p></li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>PARK: {cc['park']}</summary>
|
||||
<summary>
|
||||
EN
|
||||
</summary>
|
||||
<ul class="bg-base-100 rounded-t-none p-2">
|
||||
<li><p>EN</p></li>
|
||||
<li><p>IT</p></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<button class={["h-full w-full", logouting && "bg-gray-300"]} onclick={logout}>
|
||||
<Icon class="h-5 w-5" icon="mdi:logout"/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{#await import('./Map.svelte') then { default: Map }}
|
||||
<Map class="grow h-10 -z-20" x={cc['latitude']} y={cc['longitude']}/>
|
||||
<Map class="grow h-10 -z-20" x={cc['latitude']} y={cc['longitude']}>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<p class="!m-0">Informations</p>
|
||||
<div class="tooltip" data-tip="Type 2">
|
||||
<div class="w-5 h-5">
|
||||
<ConnType2/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full border-b border-b-gray-300"></div>
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<Icon class="h-5 w-5 text-success" icon="mdi:checkbox-marked-circle"/>
|
||||
<p class="!m-0">Disponibile</p>
|
||||
</div>
|
||||
</div>
|
||||
</Map>
|
||||
{/await}
|
||||
</div>
|
||||
<div class="z-20 mt-16 grow pointer-events-none">
|
||||
|
||||
@@ -4,16 +4,19 @@ import { type } from "arktype";
|
||||
|
||||
const QrcodeType = type("string.integer.parse");
|
||||
|
||||
export const load: LayoutLoad = async ({ params, fetch }) => {
|
||||
export const load: LayoutLoad = async ({ params, fetch, parent }) => {
|
||||
const qrcode = QrcodeType(params.qrcode);
|
||||
if (qrcode instanceof type.errors) error(400, 'invalid qrcode');
|
||||
const data = await fetch(`/api/v2/chargecontroller/${qrcode}/`, {
|
||||
const cc = await fetch(`/api/v2/chargecontroller/${qrcode}/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
const parentData = await parent();
|
||||
return {
|
||||
chargecontroller: await data.json(),
|
||||
...parentData,
|
||||
qrcode: qrcode,
|
||||
chargecontroller: await cc.json(),
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
<div class="bg-black pointer-events-auto">
|
||||
TEST CARD
|
||||
START CARD
|
||||
</div>
|
||||
8
src/routes/[qrcode]/+page.ts
Normal file
8
src/routes/[qrcode]/+page.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const parentData = await parent();
|
||||
if (parentData.user === null) return redirect(303, `${parentData.qrcode}/login`);
|
||||
return parentData;
|
||||
};
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts">
|
||||
import * as L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { onMount } from "svelte";
|
||||
import { onMount, type Snippet } from "svelte";
|
||||
import type { SvelteHTMLElements } from "svelte/elements";
|
||||
|
||||
type Props = {
|
||||
x: number,
|
||||
y: number,
|
||||
children?: Snippet,
|
||||
} & SvelteHTMLElements['div']
|
||||
|
||||
let { x, y, ...rest }: Props = $props();
|
||||
let { x, y, children, ...rest }: Props = $props();
|
||||
|
||||
let mapDiv: HTMLDivElement;
|
||||
let popupDiv: HTMLDivElement;
|
||||
let map: L.Map;
|
||||
|
||||
onMount(() => {
|
||||
@@ -20,9 +22,16 @@
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
L.marker([x, y]).addTo(map);
|
||||
const marker = L.marker([x, y]).addTo(map);
|
||||
if (children !== undefined)
|
||||
marker.bindPopup(popupDiv, { closeOnClick: false }).openPopup();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={mapDiv} {...rest}>
|
||||
</div>
|
||||
<div class="hidden">
|
||||
<div bind:this={popupDiv} class="h-full w-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
89
src/routes/[qrcode]/login/+page.svelte
Normal file
89
src/routes/[qrcode]/login/+page.svelte
Normal file
@@ -0,0 +1,89 @@
|
||||
<script>
|
||||
import { superForm, defaults, setMessage } from 'sveltekit-superforms';
|
||||
import { type } from "arktype";
|
||||
import { arktypeClient } from 'sveltekit-superforms/adapters';
|
||||
import FormInput from "$lib/FormInput.svelte";
|
||||
import PwdFormInput from "$lib/PwdFormInput.svelte";
|
||||
import { goto, invalidateAll } from "$app/navigation";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const schema = type({
|
||||
username: 'string > 3',
|
||||
password: 'string > 3',
|
||||
});
|
||||
|
||||
const schemaDefaults = {
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const form = superForm(defaults(schemaDefaults, arktypeClient(schema)), {
|
||||
SPA: true,
|
||||
validators: arktypeClient(schema),
|
||||
resetForm: false,
|
||||
async onUpdate({ form }) {
|
||||
// Equivalent to onSubmit for this context. We can validate and then execute things.
|
||||
if (form.valid) {
|
||||
// Call an external API with form.data, await the result and update form
|
||||
const response = await fetch("/api/public/1/auth/login/", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(form.data),
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(response.status);
|
||||
|
||||
if (response.status !== 200) {
|
||||
setMessage(form, {
|
||||
status: 'error',
|
||||
text: `HTTP Code ${response.status}:\n${JSON.stringify(await response.json(), null, 2)}`,
|
||||
});
|
||||
} else {
|
||||
setMessage(form, {
|
||||
status: 'success',
|
||||
text: 'Login succeded, redirecting...',
|
||||
});
|
||||
await invalidateAll();
|
||||
await goto(`/${data.qrcode}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const { message, enhance, submitting } = form;
|
||||
</script>
|
||||
|
||||
<div class="pointer-events-auto bg-gray-300/50 h-full w-full flex justify-center items-center">
|
||||
<div class="card bg-base-100 w-fit shadow-md">
|
||||
<form class="card-body" method="POST" use:enhance>
|
||||
<h2 class="card-title">Accesso Utente</h2>
|
||||
<div class="flex flex-col gap-2 my-2">
|
||||
<FormInput {form} type="text" label="Username" name="username"/>
|
||||
<PwdFormInput {form} label="Password" name="password"/>
|
||||
</div>
|
||||
<div class="card-actions justify-center">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{#if $submitting}
|
||||
<span class="loading"></span>
|
||||
{/if}
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
{#if $message}
|
||||
<div class="card bg-base-200 mt-4 card-border max-w-[80vw]">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title" class:text-error={$message['status'] === 'error'}>
|
||||
{$message['status'] === 'success' ? 'Success!' : 'Error'}
|
||||
</h2>
|
||||
<div>
|
||||
{$message['text']}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
8
src/routes/[qrcode]/login/+page.ts
Normal file
8
src/routes/[qrcode]/login/+page.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const parentData = await parent();
|
||||
if (parentData.user !== null) return redirect(303, `/${parentData.qrcode}`);
|
||||
return parentData;
|
||||
};
|
||||
3
src/routes/[qrcode]/status/+page.svelte
Normal file
3
src/routes/[qrcode]/status/+page.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="bg-black pointer-events-auto">
|
||||
STATUS CARD
|
||||
</div>
|
||||
8
src/routes/[qrcode]/status/+page.ts
Normal file
8
src/routes/[qrcode]/status/+page.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const parentData = await parent();
|
||||
if (parentData.user === null) return redirect(303, `${parentData.qrcode}/login`);
|
||||
return parentData;
|
||||
};
|
||||
Reference in New Issue
Block a user