Compare commits

...

4 Commits
0.5.0 ... 0.6.0

Author SHA1 Message Date
aa7d2dbe1b
🚀Bumped version to 0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:07:26 +02:00
01cd8d4b78
add chrome kiosk command to readme
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:07:15 +02:00
c7c1c6dc41
refactor!: cleanup userdata localstorage + components
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:06:13 +02:00
bf8b351b64
feat!: move to single route application for kiosk mode
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-19 18:02:33 +02:00
11 changed files with 226 additions and 253 deletions

View File

@ -2,9 +2,18 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [0.6.0](https://git.odit.services/lfk/kiosk/compare/0.5.0...0.6.0)
- feat!: move to single route application for kiosk mode [`bf8b351`](https://git.odit.services/lfk/kiosk/commit/bf8b351b644174a2dd39d38208a03a067de387be)
- refactor!: cleanup userdata localstorage + components [`c7c1c6d`](https://git.odit.services/lfk/kiosk/commit/c7c1c6dc41afc6ca3e66b507fd1ff50185f4e1be)
- add chrome kiosk command to readme [`01cd8d4`](https://git.odit.services/lfk/kiosk/commit/01cd8d4b78b3898d2f74f2b27cdad9e9745c1e33)
#### [0.5.0](https://git.odit.services/lfk/kiosk/compare/0.4.4...0.5.0) #### [0.5.0](https://git.odit.services/lfk/kiosk/compare/0.4.4...0.5.0)
> 19 April 2023
- feat(registration): autofocus input fields + done button [`cbfbd92`](https://git.odit.services/lfk/kiosk/commit/cbfbd92d0e4f9735a2192125f267f651ed36c9b1) - feat(registration): autofocus input fields + done button [`cbfbd92`](https://git.odit.services/lfk/kiosk/commit/cbfbd92d0e4f9735a2192125f267f651ed36c9b1)
- 🚀Bumped version to 0.5.0 [`58830c5`](https://git.odit.services/lfk/kiosk/commit/58830c5db3d9903d590f535a4099ad93a8ff6519)
- feat(registration): disabled done button for 7.5s [`5f5b03a`](https://git.odit.services/lfk/kiosk/commit/5f5b03a8a086799543cf675ccc3a973b781d1987) - feat(registration): disabled done button for 7.5s [`5f5b03a`](https://git.odit.services/lfk/kiosk/commit/5f5b03a8a086799543cf675ccc3a973b781d1987)
- feat(registration): support next input element with ENTER key [`066e67c`](https://git.odit.services/lfk/kiosk/commit/066e67c64f48b00673f2de7727acb230c94c3c13) - feat(registration): support next input element with ENTER key [`066e67c`](https://git.odit.services/lfk/kiosk/commit/066e67c64f48b00673f2de7727acb230c94c3c13)
- feat(registration): disable text select [`418fe77`](https://git.odit.services/lfk/kiosk/commit/418fe7773fbc981186909a1f7c262c3c1fa1ece2) - feat(registration): disable text select [`418fe77`](https://git.odit.services/lfk/kiosk/commit/418fe7773fbc981186909a1f7c262c3c1fa1ece2)

View File

@ -24,4 +24,9 @@ pnpm build
``` ```
docker build . docker build .
docker-compose up docker-compose up
```
## Kiosk Google Chrome
```
chrome https://run.lauf-fuer-kaya.de/kiosk/ -kiosk
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "@lfk/kiosk", "name": "@lfk/kiosk",
"version": "0.5.0", "version": "0.6.0",
"private": false, "private": false,
"license": "MIT", "license": "MIT",
"repository": "https://git.odit.services/lfk/kiosk", "repository": "https://git.odit.services/lfk/kiosk",

View File

@ -2,93 +2,69 @@ import { AuthService, OpenAPI, type ResponseAuth } from '@odit/lfk-client-js';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
type UserState = { type UserState = {
access_token: string; access_token: string;
refresh_token: string; refresh_token: string;
isLoggedIn: boolean; isLoggedIn: boolean;
refreshInterval: NodeJS.Timer | undefined refreshInterval: NodeJS.Timer | undefined;
}; };
const state: UserState = {
const userStore = () => { access_token: '',
const state: UserState = { refresh_token: '',
access_token: '', isLoggedIn: false,
refresh_token: '', refreshInterval: undefined
isLoggedIn: false,
refreshInterval: undefined
};
const { subscribe, set, update } = writable(state);
const methods = {
async login(resAuth: ResponseAuth) {
update((state: UserState) => {
if (!resAuth) {
return state;
}
state.access_token = resAuth.access_token;
state.refresh_token = resAuth.refresh_token;
state.isLoggedIn = true;
state.refreshInterval = setInterval(() => {
this.refreshAuth();
}, 2 * 60000)
localStorage.setItem('kiosk-userdata', JSON.stringify(state));
localStorage.setItem('kiosk-access_token', state.access_token);
OpenAPI.TOKEN = resAuth.access_token;
return state;
});
},
async refreshAuth() {
try {
const authRes = await AuthService.authControllerRefresh({ token: state.refresh_token }) as ResponseAuth;
OpenAPI.TOKEN = authRes.access_token;
} catch {
this.logout();
}
},
async loginFromStorage() {
console.log('loginFromStorage');
const access_token = localStorage.getItem('kiosk-access_token');
if (!access_token) {
throw new Error('Unauthorized');
}
const storagedata = localStorage.getItem('kiosk-userdata');
const userdata = JSON.parse(storagedata || '{}') as UserState;
update((state: UserState) => {
state.access_token = access_token;
state.refresh_token = userdata.refresh_token;
state.isLoggedIn = true;
state.refreshInterval = setInterval(() => {
this.refreshAuth();
}, 2 * 60000);
OpenAPI.TOKEN = userdata.access_token;
return state;
});
await this.refreshAuth();
},
async logout() {
update((state: UserState) => {
state.isLoggedIn = false;
state.access_token = '';
state.refresh_token = '';
state.refreshInterval = undefined;
localStorage.clear();
return state;
});
}
};
return {
subscribe,
set,
update,
state,
...methods
};
}; };
const userState = writable(state);
async function auth_login(resAuth: ResponseAuth) {
if (!resAuth) {
return state;
}
export default userStore(); state.access_token = resAuth.access_token;
state.refresh_token = resAuth.refresh_token;
state.isLoggedIn = true;
state.refreshInterval = setInterval(() => {
refreshAuth();
}, 2 * 60000);
localStorage.setItem('kiosk-userdata', JSON.stringify(state));
OpenAPI.TOKEN = resAuth.access_token;
userState.set(state);
return state;
}
async function refreshAuth() {
try {
const authRes = (await AuthService.authControllerRefresh({
token: state.refresh_token
})) as ResponseAuth;
OpenAPI.TOKEN = authRes.access_token;
} catch {
logout();
}
}
async function loginFromStorage() {
const storagedata = localStorage.getItem('kiosk-userdata');
const userdata = JSON.parse(storagedata || '{}') as UserState;
if (!userdata.access_token) {
throw new Error('Unauthorized');
}
state.access_token = userdata.access_token;
state.refresh_token = userdata.refresh_token;
state.isLoggedIn = true;
state.refreshInterval = setInterval(() => {
refreshAuth();
}, 2 * 60000);
OpenAPI.TOKEN = userdata.access_token;
userState.set(state);
return state;
}
async function logout() {
state.isLoggedIn = false;
state.access_token = '';
state.refresh_token = '';
state.refreshInterval = undefined;
localStorage.clear();
return state;
}
export { auth_login, logout, loginFromStorage, refreshAuth, userState };

View File

@ -2,10 +2,20 @@
import { OpenAPI } from '@odit/lfk-client-js'; import { OpenAPI } from '@odit/lfk-client-js';
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
import '../app.postcss'; import '../app.postcss';
import "@fontsource/athiti" import '@fontsource/athiti';
import Footer from '../components/footer.svelte'; import { onMount } from 'svelte';
OpenAPI.BASE = env.PUBLIC_BASE_URL || 'https://run.lauf-fuer-kaya.de'; OpenAPI.BASE = env.PUBLIC_BASE_URL || 'https://run.lauf-fuer-kaya.de';
onMount(() => {
window.addEventListener('keydown', (e) => {
if(e.altKey) e.preventDefault();
// console.log(e);
// const { key, altKey } = e;
// if (key === 'F4' && altKey) {
// e.preventDefault();
// }
});
});
</script> </script>
<div class="dark:bg-slate-900 flex flex-col h-screen"> <div class="dark:bg-slate-900 flex flex-col h-screen">

View File

@ -1,41 +1,16 @@
<!-- Hero --> <script lang="ts">
<div import { loginFromStorage, userState } from '$lib/userstore';
class="relative overflow-hidden before:absolute before:top-0 before:left-1/2 before:bg-[url('/assets/polygon.svg')] before:bg-no-repeat before:bg-top before:bg-cover before:w-full before:h-full before:-z-[1] before:transform before:-translate-x-1/2 dark:before:bg-[url('/assets/polygon-dark.svg')]" import Register from './Register.svelte';
> import { onMount } from 'svelte';
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10"> import Login from './Login.svelte';
<!-- Title -->
<div class="mt-5 max-w-2xl text-center mx-auto">
<h1 class="block font-bold text-gray-800 text-4xl md:text-5xl lg:text-6xl dark:text-gray-200">
LfK! Selfservice
<span class="bg-clip-text bg-gradient-to-tl from-blue-600 to-violet-600 text-transparent"
>Kiosk</span
>
</h1>
</div>
<!-- End Title -->
<div class="mt-5 max-w-3xl text-center mx-auto"> onMount(() => {
<p class="text-lg text-gray-600 dark:text-gray-400">Für die Anmeldung vor Ort</p> loginFromStorage();
</div> });
</script>
<!-- Buttons --> {#if $userState.isLoggedIn}
<div class="mt-8 grid gap-3 w-full sm:inline-flex sm:justify-center"> <Register />
<a {:else}
class="inline-flex justify-center items-center gap-x-3 text-center bg-gradient-to-tl from-blue-600 to-violet-600 hover:from-violet-600 hover:to-blue-600 border border-transparent text-white text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 focus:ring-offset-white py-3 px-4 dark:focus:ring-offset-gray-800" <Login />
href="./login" {/if}
>
Kiosk starten
<svg class="w-3 h-3" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M5.27921 2L10.9257 7.64645C11.1209 7.84171 11.1209 8.15829 10.9257 8.35355L5.27921 14"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</a>
</div>
<!-- End Buttons -->
</div>
</div>
<!-- End Hero -->

120
src/routes/Login.svelte Normal file
View File

@ -0,0 +1,120 @@
<script lang="ts">
import { auth_login, loginFromStorage } from '$lib/userstore';
import { AuthService } from '@odit/lfk-client-js';
import { onMount } from 'svelte';
$: username = '';
$: password = '';
$: loginError = false;
onMount(() => {
loginFromStorage();
});
async function login() {
try {
const auth = (await AuthService.authControllerLogin({
username,
password
})) as import('@odit/lfk-client-js').ResponseAuth;
loginError = false;
auth_login(auth);
} catch (error) {
loginError = true;
}
}
</script>
<!-- -->
<div
class="relative overflow-hidden before:absolute before:top-0 before:left-1/2 before:bg-[url('/assets/polygon.svg')] before:bg-no-repeat before:bg-top before:bg-cover before:w-full before:h-full before:-z-[1] before:transform before:-translate-x-1/2 dark:before:bg-[url('/assets/polygon-dark.svg')]"
>
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10">
<!-- Title -->
<div class="mt-5 max-w-2xl text-center mx-auto">
<h1 class="block font-bold text-gray-800 text-4xl md:text-5xl lg:text-6xl dark:text-gray-200">
LfK! Selfservice
<span class="bg-clip-text bg-gradient-to-tl from-blue-600 to-violet-600 text-transparent"
>Kiosk</span
>
</h1>
</div>
<!-- End Title -->
<div class="mt-5 max-w-3xl text-center mx-auto">
<p class="text-lg text-gray-600 dark:text-gray-400">Für die Anmeldung vor Ort</p>
</div>
<div class="w-full max-w-md mx-auto p-6">
<div
class="mt-7 bg-white border border-gray-200 rounded-xl shadow-sm dark:bg-gray-800 dark:border-gray-700"
>
<div class="p-4 sm:p-7">
<div class="text-center mb-8">
<h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Anmeldung</h1>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Hierfür wird ein LfK Läufersystem Account benötigt
</p>
</div>
{#if loginError}
<div
class="bg-red-500 text-sm text-white text-center font-semibold rounded-md shadow-lg mb-8"
role="alert"
>
<div class="p-4">Falscher Nutzername oder falsches Passwort</div>
</div>
{/if}
<!-- Form -->
<form on:submit|preventDefault={login}>
<div class="grid gap-y-4">
<!-- Form Group -->
<div>
<label for="username" class="block text-sm mb-2 dark:text-white">Benutzername</label
>
<div class="relative">
<input
bind:value={username}
type="username"
id="username"
name="username"
class="py-3 px-4 block w-full border-gray-200 rounded-md text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 border"
required
aria-describedby="username-error"
/>
</div>
</div>
<!-- End Form Group -->
<!-- Form Group -->
<div>
<div class="flex justify-between items-center">
<label for="password" class="block text-sm mb-2 dark:text-white">Passwort</label>
</div>
<div class="relative">
<input
bind:value={password}
type="password"
id="password"
name="password"
class="py-3 px-4 block w-full border-gray-200 rounded-md text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 border"
required
aria-describedby="password-error"
/>
</div>
</div>
<!-- End Form Group -->
<button
type="submit"
class="py-3 px-4 inline-flex justify-center items-center gap-2 rounded-md border border-transparent font-semibold bg-blue-500 text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all text-sm dark:focus:ring-offset-gray-800"
>Anmelden</button
>
</div>
</form>
<!-- End Form -->
</div>
</div>
</div>
</div>
</div>

View File

@ -8,7 +8,6 @@
$: lastname = ''; $: lastname = '';
$: doneButtonEnabled = false; $: doneButtonEnabled = false;
$: showResult = false; $: showResult = false;
$: showError = false;
let response: ResponseRunner; let response: ResponseRunner;
const group = 1; //Default to Bürgerlauf const group = 1; //Default to Bürgerlauf
@ -37,7 +36,6 @@
lastname, lastname,
group group
})) as ResponseRunner; })) as ResponseRunner;
showError = false;
showResult = true; showResult = true;
setTimeout(() => { setTimeout(() => {
doneButtonEnabled = true; doneButtonEnabled = true;
@ -47,8 +45,9 @@
}, 7500); }, 7500);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
showError = true;
showResult = false; showResult = false;
localStorage.clear();
location.reload();
} }
firstname = ''; firstname = '';
lastname = ''; lastname = '';

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -1,95 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import userstore from '$lib/userstore';
import { AuthService } from '@odit/lfk-client-js';
$: username = '';
$: password = '';
$: loginError = false;
async function login() {
try {
const auth = (await AuthService.authControllerLogin({
username,
password
})) as import('@odit/lfk-client-js').ResponseAuth;
loginError=false;
await userstore.login(auth);
goto('./registration', {replaceState: true})
} catch (error) {
console.log(error);
loginError = true;
}
}
</script>
<div class="dark:bg-slate-900 bg-gray-100 flex h-full items-center py-16">
<div class="w-full max-w-md mx-auto p-6">
<div
class="mt-7 bg-white border border-gray-200 rounded-xl shadow-sm dark:bg-gray-800 dark:border-gray-700"
>
<div class="p-4 sm:p-7">
<div class="text-center mb-8">
<h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Anmeldung</h1>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Hierfür wird ein LfK Läufersystem Account benötigt
</p>
</div>
{#if loginError}
<div class="bg-red-500 text-sm text-white text-center font-semibold rounded-md shadow-lg mb-8" role="alert">
<div class="p-4">Falscher Nutzername oder falsches Passwort</div>
</div>
{/if}
<!-- Form -->
<form on:submit|preventDefault={login}>
<div class="grid gap-y-4">
<!-- Form Group -->
<div>
<label for="username" class="block text-sm mb-2 dark:text-white">Benutzername</label>
<div class="relative">
<input
bind:value={username}
type="username"
id="username"
name="username"
class="py-3 px-4 block w-full border-gray-200 rounded-md text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 border"
required
aria-describedby="username-error"
/>
</div>
</div>
<!-- End Form Group -->
<!-- Form Group -->
<div>
<div class="flex justify-between items-center">
<label for="password" class="block text-sm mb-2 dark:text-white">Passwort</label>
</div>
<div class="relative">
<input
bind:value={password}
type="password"
id="password"
name="password"
class="py-3 px-4 block w-full border-gray-200 rounded-md text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 border"
required
aria-describedby="password-error"
/>
</div>
</div>
<!-- End Form Group -->
<button
type="submit"
class="py-3 px-4 inline-flex justify-center items-center gap-2 rounded-md border border-transparent font-semibold bg-blue-500 text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all text-sm dark:focus:ring-offset-gray-800"
>Anmelden</button
>
</div>
</form>
<!-- End Form -->
</div>
</div>
</div>
</div>

View File

@ -1,26 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import userstore from '$lib/userstore';
import { onMount } from 'svelte';
$: loginRendered = false;
onMount(async () => {
try {
await userstore.loginFromStorage();
loginRendered = true;
} catch (error) {
console.log(error);
userstore.logout();
goto('./login', {replaceState: true})
}
});
</script>
{#if loginRendered}
<slot />
{:else}
<div class="mx-auto mt-20">
<h1 class="block text-center text-2xl font-bold text-gray-800 dark:text-white">Lade Anmeldedaten...</h1>
</div>
{/if}